1 /*
2  * e-attachment.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  *
17  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
18  *
19  */
20 
21 #include "evolution-config.h"
22 
23 #include "e-attachment.h"
24 
25 #include <errno.h>
26 #include <glib/gi18n.h>
27 #include <glib/gstdio.h>
28 
29 #ifdef HAVE_AUTOAR
30 #include <gnome-autoar/gnome-autoar.h>
31 #endif
32 
33 #include <libedataserver/libedataserver.h>
34 
35 #include "e-icon-factory.h"
36 #include "e-mktemp.h"
37 #include "e-misc-utils.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	"process-stop"
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 	GMutex property_lock;
62 
63 	GFile *file;
64 	GIcon *icon;
65 	GFileInfo *file_info;
66 	GCancellable *cancellable;
67 	CamelMimePart *mime_part;
68 	guint emblem_timeout_id;
69 	gchar *disposition;
70 	gint percent;
71 	gint64 last_percent_notify; /* to avoid excessive notifications */
72 
73 	guint can_show : 1;
74 	guint loading : 1;
75 	guint saving : 1;
76 	guint initially_shown : 1;
77 
78 	guint save_self      : 1;
79 	guint save_extracted : 1;
80 
81 	CamelCipherValidityEncrypt encrypted;
82 	CamelCipherValiditySign signed_;
83 
84 	/* These are IDs for idle callbacks,
85 	 * protected by the idle_lock mutex. */
86 	GMutex idle_lock;
87 	guint update_icon_column_idle_id;
88 	guint update_progress_columns_idle_id;
89 	guint update_file_info_columns_idle_id;
90 };
91 
92 enum {
93 	PROP_0,
94 	PROP_CAN_SHOW,
95 	PROP_DISPOSITION,
96 	PROP_ENCRYPTED,
97 	PROP_FILE,
98 	PROP_FILE_INFO,
99 	PROP_ICON,
100 	PROP_LOADING,
101 	PROP_MIME_PART,
102 	PROP_PERCENT,
103 	PROP_SAVE_SELF,
104 	PROP_SAVE_EXTRACTED,
105 	PROP_SAVING,
106 	PROP_INITIALLY_SHOWN,
107 	PROP_SIGNED
108 };
109 
110 enum {
111 	LOAD_FAILED,
112 	UPDATE_FILE_INFO,
113 	UPDATE_ICON,
114 	UPDATE_PROGRESS,
115 	LAST_SIGNAL
116 };
117 
118 static guint signals[LAST_SIGNAL];
119 
G_DEFINE_TYPE(EAttachment,e_attachment,G_TYPE_OBJECT)120 G_DEFINE_TYPE (
121 	EAttachment,
122 	e_attachment,
123 	G_TYPE_OBJECT)
124 
125 static gboolean
126 create_system_thumbnail (EAttachment *attachment,
127                          GIcon **icon)
128 {
129 	GFile *file;
130 	GFile *icon_file;
131 	gchar *file_path = NULL;
132 	gchar *thumbnail = NULL;
133 	gboolean success = FALSE;
134 
135 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
136 	g_return_val_if_fail (icon != NULL, FALSE);
137 
138 	file = e_attachment_ref_file (attachment);
139 	if (file != NULL)
140 		file_path = g_file_get_path (file);
141 
142 	if (file_path != NULL) {
143 		thumbnail = e_icon_factory_create_thumbnail (file_path);
144 		g_free (file_path);
145 	}
146 
147 	if (thumbnail == NULL)
148 		goto exit;
149 
150 	icon_file = g_file_new_for_path (thumbnail);
151 
152 	if (*icon != NULL)
153 		g_object_unref (*icon);
154 
155 	*icon = g_file_icon_new (icon_file);
156 
157 	g_object_unref (icon_file);
158 
159 	if (file != NULL) {
160 		GFileInfo *file_info;
161 		const gchar *attribute;
162 
163 		file_info = e_attachment_ref_file_info (attachment);
164 		attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH;
165 
166 		if (file_info != NULL) {
167 			g_file_info_set_attribute_byte_string (
168 				file_info, attribute, thumbnail);
169 			g_object_unref (file_info);
170 		}
171 	}
172 
173 	g_free (thumbnail);
174 
175 	success = TRUE;
176 
177 exit:
178 	g_clear_object (&file);
179 
180 	return success;
181 }
182 
183 static gchar *
attachment_get_default_charset(void)184 attachment_get_default_charset (void)
185 {
186 	GSettings *settings;
187 	gchar *charset;
188 
189 	/* XXX This doesn't really belong here. */
190 
191 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
192 	charset = g_settings_get_string (settings, "composer-charset");
193 	if (charset == NULL || *charset == '\0') {
194 		g_free (charset);
195 		/* FIXME This was "/apps/evolution/mail/format/charset",
196 		 *       not sure it relates to "charset" */
197 		charset = g_settings_get_string (settings, "charset");
198 		if (charset == NULL || *charset == '\0') {
199 			g_free (charset);
200 			charset = NULL;
201 		}
202 	}
203 	g_object_unref (settings);
204 
205 	if (charset == NULL)
206 		charset = g_strdup (camel_iconv_locale_charset ());
207 
208 	if (charset == NULL)
209 		charset = g_strdup ("us-ascii");
210 
211 	return charset;
212 }
213 
214 static GFile*
attachment_get_temporary(GError ** error)215 attachment_get_temporary (GError **error)
216 {
217 	gchar *template;
218 	gchar *path;
219 	GFile *temp_directory;
220 
221 	errno = 0;
222 
223 	/* Save the file to a temporary directory.
224 	 * We use a directory so the files can retain their basenames.
225 	 * XXX This could trigger a blocking temp directory cleanup. */
226 	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
227 	path = e_mkdtemp (template);
228 	g_free (template);
229 
230 	/* XXX Let's hope errno got set properly. */
231 	if (path == NULL) {
232 		g_set_error (
233 			error, G_FILE_ERROR,
234 			g_file_error_from_errno (errno),
235 			"%s", g_strerror (errno));
236 		return NULL;
237 	}
238 
239 	temp_directory = g_file_new_for_path (path);
240 	g_free (path);
241 
242 	return temp_directory;
243 }
244 
245 
246 static gboolean
attachment_update_file_info_columns_idle_cb(gpointer weak_ref)247 attachment_update_file_info_columns_idle_cb (gpointer weak_ref)
248 {
249 	EAttachment *attachment;
250 	GFileInfo *file_info;
251 	const gchar *content_type;
252 	const gchar *display_name;
253 	gchar *content_desc;
254 	gchar *display_size;
255 	gchar *description;
256 	gchar *caption;
257 	goffset size;
258 
259 	attachment = g_weak_ref_get (weak_ref);
260 	if (attachment == NULL)
261 		goto exit;
262 
263 	g_mutex_lock (&attachment->priv->idle_lock);
264 	attachment->priv->update_file_info_columns_idle_id = 0;
265 	g_mutex_unlock (&attachment->priv->idle_lock);
266 
267 	file_info = e_attachment_ref_file_info (attachment);
268 	if (file_info == NULL)
269 		goto exit;
270 
271 	content_type = g_file_info_get_content_type (file_info);
272 	display_name = g_file_info_get_display_name (file_info);
273 	size = g_file_info_get_size (file_info);
274 
275 	content_desc = g_content_type_get_description (content_type);
276 	display_size = g_format_size (size);
277 
278 	description = e_attachment_dup_description (attachment);
279 	if (description == NULL || *description == '\0') {
280 		g_free (description);
281 		description = g_strdup (display_name);
282 	}
283 
284 	if (size > 0)
285 		caption = g_strdup_printf ("%s\n(%s)", description, display_size);
286 	else
287 		caption = g_strdup (description);
288 
289 	g_signal_emit (attachment, signals[UPDATE_FILE_INFO], 0, caption, content_desc, description, (gint64) size);
290 
291 	g_free (content_desc);
292 	g_free (display_size);
293 	g_free (description);
294 	g_free (caption);
295 
296 	g_clear_object (&file_info);
297 
298 exit:
299 	g_clear_object (&attachment);
300 
301 	return FALSE;
302 }
303 
304 static void
attachment_update_file_info_columns(EAttachment * attachment)305 attachment_update_file_info_columns (EAttachment *attachment)
306 {
307 	g_mutex_lock (&attachment->priv->idle_lock);
308 
309 	if (attachment->priv->update_file_info_columns_idle_id == 0) {
310 		guint idle_id;
311 
312 		idle_id = g_idle_add_full (
313 			G_PRIORITY_HIGH_IDLE,
314 			attachment_update_file_info_columns_idle_cb,
315 			e_weak_ref_new (attachment),
316 			(GDestroyNotify) e_weak_ref_free);
317 		attachment->priv->update_file_info_columns_idle_id = idle_id;
318 	}
319 
320 	g_mutex_unlock (&attachment->priv->idle_lock);
321 }
322 
323 static gboolean
attachment_update_icon_column_idle_cb(gpointer weak_ref)324 attachment_update_icon_column_idle_cb (gpointer weak_ref)
325 {
326 	EAttachment *attachment;
327 	GFileInfo *file_info;
328 	GCancellable *cancellable;
329 	GIcon *icon = NULL;
330 	const gchar *emblem_name = NULL;
331 	const gchar *thumbnail_path = NULL;
332 
333 	attachment = g_weak_ref_get (weak_ref);
334 	if (attachment == NULL)
335 		goto exit;
336 
337 	g_mutex_lock (&attachment->priv->idle_lock);
338 	attachment->priv->update_icon_column_idle_id = 0;
339 	g_mutex_unlock (&attachment->priv->idle_lock);
340 
341 	cancellable = attachment->priv->cancellable;
342 	file_info = e_attachment_ref_file_info (attachment);
343 
344 	if (file_info != NULL) {
345 		icon = g_file_info_get_icon (file_info);
346 		/* add the reference here, thus the create_system_thumbnail() can unref the *icon. */
347 		if (icon)
348 			g_object_ref (icon);
349 		thumbnail_path = g_file_info_get_attribute_byte_string (
350 			file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
351 	}
352 
353 	if (e_attachment_is_mail_note (attachment)) {
354 		g_clear_object (&icon);
355 		icon = g_themed_icon_new ("evolution-memos");
356 
357 	/* Prefer the thumbnail if we have one. */
358 	} else if (thumbnail_path != NULL && *thumbnail_path != '\0') {
359 		GFile *file;
360 
361 		file = g_file_new_for_path (thumbnail_path);
362 		icon = g_file_icon_new (file);
363 		g_object_unref (file);
364 
365 	/* Try the system thumbnailer. */
366 	} else if (create_system_thumbnail (attachment, &icon)) {
367 		/* Nothing to do, just use the icon. */
368 
369 	/* Else use the standard icon for the content type. */
370 	} else if (icon != NULL) {
371 		/* Nothing to do, just use the already reffed icon. */
372 
373 	/* Last ditch fallback.  (GFileInfo not yet loaded?) */
374 	} else
375 		icon = g_themed_icon_new (DEFAULT_ICON_NAME);
376 
377 	/* Pick an emblem, limit one.  Choices listed by priority. */
378 
379 	if (g_cancellable_is_cancelled (cancellable))
380 		emblem_name = EMBLEM_CANCELLED;
381 
382 	else if (e_attachment_get_loading (attachment))
383 		emblem_name = EMBLEM_LOADING;
384 
385 	else if (e_attachment_get_saving (attachment))
386 		emblem_name = EMBLEM_SAVING;
387 
388 	else if (e_attachment_get_encrypted (attachment))
389 		switch (e_attachment_get_encrypted (attachment)) {
390 			case CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK:
391 				emblem_name = EMBLEM_ENCRYPT_WEAK;
392 				break;
393 
394 			case CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED:
395 				emblem_name = EMBLEM_ENCRYPT_UNKNOWN;
396 				break;
397 
398 			case CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG:
399 				emblem_name = EMBLEM_ENCRYPT_STRONG;
400 				break;
401 
402 			default:
403 				g_warn_if_reached ();
404 				break;
405 		}
406 
407 	else if (e_attachment_get_signed (attachment))
408 		switch (e_attachment_get_signed (attachment)) {
409 			case CAMEL_CIPHER_VALIDITY_SIGN_GOOD:
410 				emblem_name = EMBLEM_SIGN_GOOD;
411 				break;
412 
413 			case CAMEL_CIPHER_VALIDITY_SIGN_BAD:
414 				emblem_name = EMBLEM_SIGN_BAD;
415 				break;
416 
417 			case CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN:
418 			case CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY:
419 				emblem_name = EMBLEM_SIGN_UNKNOWN;
420 				break;
421 
422 			default:
423 				g_warn_if_reached ();
424 				break;
425 		}
426 
427 	if (emblem_name != NULL) {
428 		GIcon *emblemed_icon;
429 		GEmblem *emblem;
430 
431 		emblemed_icon = g_themed_icon_new (emblem_name);
432 		emblem = g_emblem_new (emblemed_icon);
433 		g_object_unref (emblemed_icon);
434 
435 		emblemed_icon = g_emblemed_icon_new (icon, emblem);
436 		g_object_unref (emblem);
437 		g_object_unref (icon);
438 
439 		icon = emblemed_icon;
440 	}
441 
442 	g_signal_emit (attachment, signals[UPDATE_ICON], 0, icon);
443 
444 	/* Cache the icon to reuse for things like drag-n-drop. */
445 	if (attachment->priv->icon != NULL)
446 		g_object_unref (attachment->priv->icon);
447 	attachment->priv->icon = icon;
448 	g_object_notify (G_OBJECT (attachment), "icon");
449 
450 	g_clear_object (&file_info);
451 
452 exit:
453 	g_clear_object (&attachment);
454 
455 	return FALSE;
456 }
457 
458 static void
attachment_update_icon_column(EAttachment * attachment)459 attachment_update_icon_column (EAttachment *attachment)
460 {
461 	g_mutex_lock (&attachment->priv->idle_lock);
462 
463 	if (attachment->priv->update_icon_column_idle_id == 0) {
464 		guint idle_id;
465 
466 		idle_id = g_idle_add_full (
467 			G_PRIORITY_HIGH_IDLE,
468 			attachment_update_icon_column_idle_cb,
469 			e_weak_ref_new (attachment),
470 			(GDestroyNotify) e_weak_ref_free);
471 		attachment->priv->update_icon_column_idle_id = idle_id;
472 	}
473 
474 	g_mutex_unlock (&attachment->priv->idle_lock);
475 }
476 
477 static gboolean
attachment_update_progress_columns_idle_cb(gpointer weak_ref)478 attachment_update_progress_columns_idle_cb (gpointer weak_ref)
479 {
480 	EAttachment *attachment;
481 	gboolean loading;
482 	gboolean saving;
483 	gint percent;
484 
485 	attachment = g_weak_ref_get (weak_ref);
486 	if (attachment == NULL)
487 		goto exit;
488 
489 	g_mutex_lock (&attachment->priv->idle_lock);
490 	attachment->priv->update_progress_columns_idle_id = 0;
491 	g_mutex_unlock (&attachment->priv->idle_lock);
492 
493 	/* Don't show progress bars until we have progress to report. */
494 	percent = e_attachment_get_percent (attachment);
495 	loading = e_attachment_get_loading (attachment) && (percent > 0);
496 	saving = e_attachment_get_saving (attachment) && (percent > 0);
497 
498 	g_signal_emit (attachment, signals[UPDATE_PROGRESS], 0, loading, saving, percent);
499 
500 exit:
501 	g_clear_object (&attachment);
502 
503 	return FALSE;
504 }
505 
506 static void
attachment_update_progress_columns(EAttachment * attachment)507 attachment_update_progress_columns (EAttachment *attachment)
508 {
509 	g_mutex_lock (&attachment->priv->idle_lock);
510 
511 	if (attachment->priv->update_progress_columns_idle_id == 0) {
512 		guint idle_id;
513 
514 		idle_id = g_idle_add_full (
515 			G_PRIORITY_HIGH_IDLE,
516 			attachment_update_progress_columns_idle_cb,
517 			e_weak_ref_new (attachment),
518 			(GDestroyNotify) e_weak_ref_free);
519 		attachment->priv->update_progress_columns_idle_id = idle_id;
520 	}
521 
522 	g_mutex_unlock (&attachment->priv->idle_lock);
523 }
524 
525 static void
attachment_set_loading(EAttachment * attachment,gboolean loading)526 attachment_set_loading (EAttachment *attachment,
527                         gboolean loading)
528 {
529 	attachment->priv->percent = 0;
530 	attachment->priv->loading = loading;
531 	attachment->priv->last_percent_notify = 0;
532 
533 	g_object_freeze_notify (G_OBJECT (attachment));
534 	g_object_notify (G_OBJECT (attachment), "percent");
535 	g_object_notify (G_OBJECT (attachment), "loading");
536 	g_object_thaw_notify (G_OBJECT (attachment));
537 }
538 
539 static void
attachment_set_saving(EAttachment * attachment,gboolean saving)540 attachment_set_saving (EAttachment *attachment,
541                        gboolean saving)
542 {
543 	attachment->priv->percent = 0;
544 	attachment->priv->saving = saving;
545 	attachment->priv->last_percent_notify = 0;
546 }
547 
548 static void
attachment_progress_cb(goffset current_num_bytes,goffset total_num_bytes,EAttachment * attachment)549 attachment_progress_cb (goffset current_num_bytes,
550                         goffset total_num_bytes,
551                         EAttachment *attachment)
552 {
553 	gint new_percent;
554 
555 	/* Avoid dividing by zero. */
556 	if (total_num_bytes == 0)
557 		return;
558 
559 	/* do not notify too often, 5 times per second is sufficient */
560 	if (g_get_monotonic_time () - attachment->priv->last_percent_notify < 200000)
561 		return;
562 
563 	attachment->priv->last_percent_notify = g_get_monotonic_time ();
564 
565 	new_percent = (current_num_bytes * 100) / total_num_bytes;
566 
567 	if (new_percent != attachment->priv->percent)
568 		attachment->priv->percent = new_percent;
569 }
570 
571 static gboolean
attachment_cancelled_timeout_cb(gpointer user_data)572 attachment_cancelled_timeout_cb (gpointer user_data)
573 {
574 	EAttachment *attachment;
575 
576 	attachment = E_ATTACHMENT (user_data);
577 	attachment->priv->emblem_timeout_id = 0;
578 	g_cancellable_reset (attachment->priv->cancellable);
579 
580 	attachment_update_icon_column (attachment);
581 
582 	return FALSE;
583 }
584 
585 static void
attachment_cancelled_cb(EAttachment * attachment)586 attachment_cancelled_cb (EAttachment *attachment)
587 {
588 	/* Reset the GCancellable after one second.  This causes a
589 	 * cancel emblem to be briefly shown on the attachment icon
590 	 * as visual feedback that an operation was cancelled. */
591 
592 	if (attachment->priv->emblem_timeout_id > 0)
593 		g_source_remove (attachment->priv->emblem_timeout_id);
594 
595 	attachment->priv->emblem_timeout_id = e_named_timeout_add_seconds (
596 		1, attachment_cancelled_timeout_cb, attachment);
597 
598 	attachment_update_icon_column (attachment);
599 }
600 
601 static void
attachment_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)602 attachment_set_property (GObject *object,
603                          guint property_id,
604                          const GValue *value,
605                          GParamSpec *pspec)
606 {
607 	switch (property_id) {
608 		case PROP_CAN_SHOW:
609 			e_attachment_set_can_show (
610 				E_ATTACHMENT (object),
611 				g_value_get_boolean (value));
612 			return;
613 
614 		case PROP_DISPOSITION:
615 			e_attachment_set_disposition (
616 				E_ATTACHMENT (object),
617 				g_value_get_string (value));
618 			return;
619 
620 		case PROP_ENCRYPTED:
621 			e_attachment_set_encrypted (
622 				E_ATTACHMENT (object),
623 				g_value_get_int (value));
624 			return;
625 
626 		case PROP_FILE:
627 			e_attachment_set_file (
628 				E_ATTACHMENT (object),
629 				g_value_get_object (value));
630 			return;
631 
632 		case PROP_INITIALLY_SHOWN:
633 			e_attachment_set_initially_shown (
634 				E_ATTACHMENT (object),
635 				g_value_get_boolean (value));
636 			return;
637 
638 		case PROP_MIME_PART:
639 			e_attachment_set_mime_part (
640 				E_ATTACHMENT (object),
641 				g_value_get_object (value));
642 			return;
643 
644 		case PROP_SIGNED:
645 			e_attachment_set_signed (
646 				E_ATTACHMENT (object),
647 				g_value_get_int (value));
648 			return;
649 
650 		case PROP_SAVE_SELF:
651 			e_attachment_set_save_self (
652 				E_ATTACHMENT (object),
653 				g_value_get_boolean (value));
654 			return;
655 
656 		case PROP_SAVE_EXTRACTED:
657 			e_attachment_set_save_extracted (
658 				E_ATTACHMENT (object),
659 				g_value_get_boolean (value));
660 			return;
661 	}
662 
663 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
664 }
665 
666 static void
attachment_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)667 attachment_get_property (GObject *object,
668                          guint property_id,
669                          GValue *value,
670                          GParamSpec *pspec)
671 {
672 	switch (property_id) {
673 		case PROP_CAN_SHOW:
674 			g_value_set_boolean (
675 				value,
676 				e_attachment_get_can_show (
677 				E_ATTACHMENT (object)));
678 			return;
679 
680 		case PROP_DISPOSITION:
681 			g_value_set_string (
682 				value,
683 				e_attachment_get_disposition (
684 				E_ATTACHMENT (object)));
685 			return;
686 
687 		case PROP_ENCRYPTED:
688 			g_value_set_int (
689 				value,
690 				e_attachment_get_encrypted (
691 				E_ATTACHMENT (object)));
692 			return;
693 
694 		case PROP_FILE:
695 			g_value_take_object (
696 				value,
697 				e_attachment_ref_file (
698 				E_ATTACHMENT (object)));
699 			return;
700 
701 		case PROP_FILE_INFO:
702 			g_value_take_object (
703 				value,
704 				e_attachment_ref_file_info (
705 				E_ATTACHMENT (object)));
706 			return;
707 
708 		case PROP_ICON:
709 			g_value_take_object (
710 				value,
711 				e_attachment_ref_icon (
712 				E_ATTACHMENT (object)));
713 			return;
714 
715 		case PROP_INITIALLY_SHOWN:
716 			g_value_set_boolean (
717 				value,
718 				e_attachment_get_initially_shown (
719 				E_ATTACHMENT (object)));
720 			return;
721 
722 		case PROP_LOADING:
723 			g_value_set_boolean (
724 				value,
725 				e_attachment_get_loading (
726 				E_ATTACHMENT (object)));
727 			return;
728 
729 		case PROP_MIME_PART:
730 			g_value_take_object (
731 				value,
732 				e_attachment_ref_mime_part (
733 				E_ATTACHMENT (object)));
734 			return;
735 
736 		case PROP_PERCENT:
737 			g_value_set_int (
738 				value,
739 				e_attachment_get_percent (
740 				E_ATTACHMENT (object)));
741 			return;
742 
743 		case PROP_SAVE_SELF:
744 			g_value_set_boolean (
745 				value,
746 				e_attachment_get_save_self (
747 				E_ATTACHMENT (object)));
748 			return;
749 
750 		case PROP_SAVE_EXTRACTED:
751 			g_value_set_boolean (
752 				value,
753 				e_attachment_get_save_extracted (
754 				E_ATTACHMENT (object)));
755 			return;
756 
757 		case PROP_SAVING:
758 			g_value_set_boolean (
759 				value,
760 				e_attachment_get_saving (
761 				E_ATTACHMENT (object)));
762 			return;
763 
764 		case PROP_SIGNED:
765 			g_value_set_int (
766 				value,
767 				e_attachment_get_signed (
768 				E_ATTACHMENT (object)));
769 			return;
770 	}
771 
772 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
773 }
774 
775 static void
attachment_dispose(GObject * object)776 attachment_dispose (GObject *object)
777 {
778 	EAttachmentPrivate *priv;
779 
780 	priv = E_ATTACHMENT_GET_PRIVATE (object);
781 
782 	g_clear_object (&priv->file);
783 	g_clear_object (&priv->icon);
784 	g_clear_object (&priv->file_info);
785 	g_clear_object (&priv->cancellable);
786 	g_clear_object (&priv->mime_part);
787 
788 	if (priv->emblem_timeout_id > 0) {
789 		g_source_remove (priv->emblem_timeout_id);
790 		priv->emblem_timeout_id = 0;
791 	}
792 
793 	/* Chain up to parent's dispose() method. */
794 	G_OBJECT_CLASS (e_attachment_parent_class)->dispose (object);
795 }
796 
797 static void
attachment_finalize(GObject * object)798 attachment_finalize (GObject *object)
799 {
800 	EAttachmentPrivate *priv;
801 
802 	priv = E_ATTACHMENT_GET_PRIVATE (object);
803 
804 	if (priv->update_icon_column_idle_id > 0)
805 		g_source_remove (priv->update_icon_column_idle_id);
806 
807 	if (priv->update_progress_columns_idle_id > 0)
808 		g_source_remove (priv->update_progress_columns_idle_id);
809 
810 	if (priv->update_file_info_columns_idle_id > 0)
811 		g_source_remove (priv->update_file_info_columns_idle_id);
812 
813 	g_mutex_clear (&priv->property_lock);
814 	g_mutex_clear (&priv->idle_lock);
815 
816 	g_free (priv->disposition);
817 
818 	/* Chain up to parent's finalize() method. */
819 	G_OBJECT_CLASS (e_attachment_parent_class)->finalize (object);
820 }
821 
822 static void
e_attachment_class_init(EAttachmentClass * class)823 e_attachment_class_init (EAttachmentClass *class)
824 {
825 	GObjectClass *object_class;
826 
827 	g_type_class_add_private (class, sizeof (EAttachmentPrivate));
828 
829 	object_class = G_OBJECT_CLASS (class);
830 	object_class->set_property = attachment_set_property;
831 	object_class->get_property = attachment_get_property;
832 	object_class->dispose = attachment_dispose;
833 	object_class->finalize = attachment_finalize;
834 
835 	g_object_class_install_property (
836 		object_class,
837 		PROP_CAN_SHOW,
838 		g_param_spec_boolean (
839 			"can-show",
840 			"Can Show",
841 			NULL,
842 			FALSE,
843 			G_PARAM_READWRITE |
844 			G_PARAM_CONSTRUCT));
845 
846 	g_object_class_install_property (
847 		object_class,
848 		PROP_DISPOSITION,
849 		g_param_spec_string (
850 			"disposition",
851 			"Disposition",
852 			NULL,
853 			"attachment",
854 			G_PARAM_READWRITE |
855 			G_PARAM_CONSTRUCT));
856 
857 	/* FIXME Define a GEnumClass for this. */
858 	g_object_class_install_property (
859 		object_class,
860 		PROP_ENCRYPTED,
861 		g_param_spec_int (
862 			"encrypted",
863 			"Encrypted",
864 			NULL,
865 			CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
866 			CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG,
867 			CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
868 			G_PARAM_READWRITE |
869 			G_PARAM_CONSTRUCT));
870 
871 	g_object_class_install_property (
872 		object_class,
873 		PROP_FILE,
874 		g_param_spec_object (
875 			"file",
876 			"File",
877 			NULL,
878 			G_TYPE_FILE,
879 			G_PARAM_READWRITE |
880 			G_PARAM_CONSTRUCT));
881 
882 	g_object_class_install_property (
883 		object_class,
884 		PROP_FILE_INFO,
885 		g_param_spec_object (
886 			"file-info",
887 			"File Info",
888 			NULL,
889 			G_TYPE_FILE_INFO,
890 			G_PARAM_READABLE));
891 
892 	g_object_class_install_property (
893 		object_class,
894 		PROP_ICON,
895 		g_param_spec_object (
896 			"icon",
897 			"Icon",
898 			NULL,
899 			G_TYPE_ICON,
900 			G_PARAM_READABLE));
901 
902 	g_object_class_install_property (
903 		object_class,
904 		PROP_LOADING,
905 		g_param_spec_boolean (
906 			"loading",
907 			"Loading",
908 			NULL,
909 			FALSE,
910 			G_PARAM_READABLE));
911 
912 	g_object_class_install_property (
913 		object_class,
914 		PROP_MIME_PART,
915 		g_param_spec_object (
916 			"mime-part",
917 			"MIME Part",
918 			NULL,
919 			CAMEL_TYPE_MIME_PART,
920 			G_PARAM_READWRITE));
921 
922 	g_object_class_install_property (
923 		object_class,
924 		PROP_PERCENT,
925 		g_param_spec_int (
926 			"percent",
927 			"Percent",
928 			NULL,
929 			0,
930 			100,
931 			0,
932 			G_PARAM_READABLE));
933 
934 	g_object_class_install_property (
935 		object_class,
936 		PROP_SAVE_SELF,
937 		g_param_spec_boolean (
938 			"save-self",
939 			"Save self",
940 			NULL,
941 			TRUE,
942 			G_PARAM_READWRITE));
943 
944 	g_object_class_install_property (
945 		object_class,
946 		PROP_SAVE_EXTRACTED,
947 		g_param_spec_boolean (
948 			"save-extracted",
949 			"Save extracted",
950 			NULL,
951 			FALSE,
952 			G_PARAM_READWRITE));
953 
954 	g_object_class_install_property (
955 		object_class,
956 		PROP_SAVING,
957 		g_param_spec_boolean (
958 			"saving",
959 			"Saving",
960 			NULL,
961 			FALSE,
962 			G_PARAM_READABLE));
963 
964 	g_object_class_install_property (
965 		object_class,
966 		PROP_INITIALLY_SHOWN,
967 		g_param_spec_boolean (
968 			"initially-shown",
969 			"Initially Shown",
970 			NULL,
971 			FALSE,
972 			G_PARAM_READWRITE |
973 			G_PARAM_CONSTRUCT));
974 
975 	/* FIXME Define a GEnumClass for this. */
976 	g_object_class_install_property (
977 		object_class,
978 		PROP_SIGNED,
979 		g_param_spec_int (
980 			"signed",
981 			"Signed",
982 			NULL,
983 			CAMEL_CIPHER_VALIDITY_SIGN_NONE,
984 			CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY,
985 			CAMEL_CIPHER_VALIDITY_SIGN_NONE,
986 			G_PARAM_READWRITE |
987 			G_PARAM_CONSTRUCT));
988 
989 	signals[UPDATE_FILE_INFO] = g_signal_new (
990 		"update-file-info",
991 		G_TYPE_FROM_CLASS (class),
992 		G_SIGNAL_RUN_LAST,
993 		G_STRUCT_OFFSET (EAttachmentClass, update_file_info),
994 		NULL, NULL, NULL,
995 		G_TYPE_NONE, 4,
996 		G_TYPE_STRING,
997 		G_TYPE_STRING,
998 		G_TYPE_STRING,
999 		G_TYPE_INT64);
1000 
1001 	signals[UPDATE_ICON] = g_signal_new (
1002 		"update-icon",
1003 		G_TYPE_FROM_CLASS (class),
1004 		G_SIGNAL_RUN_LAST,
1005 		G_STRUCT_OFFSET (EAttachmentClass, update_icon),
1006 		NULL, NULL, NULL,
1007 		G_TYPE_NONE, 1,
1008 		G_TYPE_ICON);
1009 
1010 	signals[UPDATE_PROGRESS] = g_signal_new (
1011 		"update-progress",
1012 		G_TYPE_FROM_CLASS (class),
1013 		G_SIGNAL_RUN_LAST,
1014 		G_STRUCT_OFFSET (EAttachmentClass, update_progress),
1015 		NULL, NULL, NULL,
1016 		G_TYPE_NONE, 3,
1017 		G_TYPE_BOOLEAN,
1018 		G_TYPE_BOOLEAN,
1019 		G_TYPE_INT);
1020 
1021 	signals[LOAD_FAILED] = g_signal_new (
1022 		"load-failed",
1023 		G_TYPE_FROM_CLASS (class),
1024 		G_SIGNAL_RUN_LAST,
1025 		G_STRUCT_OFFSET (EAttachmentClass, load_failed),
1026 		NULL, NULL, NULL,
1027 		G_TYPE_NONE, 0,
1028 		G_TYPE_NONE);
1029 }
1030 
1031 static void
e_attachment_init(EAttachment * attachment)1032 e_attachment_init (EAttachment *attachment)
1033 {
1034 	attachment->priv = E_ATTACHMENT_GET_PRIVATE (attachment);
1035 	attachment->priv->cancellable = g_cancellable_new ();
1036 	attachment->priv->encrypted = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE;
1037 	attachment->priv->signed_ = CAMEL_CIPHER_VALIDITY_SIGN_NONE;
1038 
1039 	g_mutex_init (&attachment->priv->property_lock);
1040 	g_mutex_init (&attachment->priv->idle_lock);
1041 
1042 	e_signal_connect_notify (
1043 		attachment, "notify::encrypted",
1044 		G_CALLBACK (attachment_update_icon_column), NULL);
1045 
1046 	g_signal_connect (
1047 		attachment, "notify::file-info",
1048 		G_CALLBACK (attachment_update_file_info_columns), NULL);
1049 
1050 	g_signal_connect (
1051 		attachment, "notify::file-info",
1052 		G_CALLBACK (attachment_update_icon_column), NULL);
1053 
1054 	e_signal_connect_notify (
1055 		attachment, "notify::loading",
1056 		G_CALLBACK (attachment_update_icon_column), NULL);
1057 
1058 	e_signal_connect_notify (
1059 		attachment, "notify::loading",
1060 		G_CALLBACK (attachment_update_progress_columns), NULL);
1061 
1062 	e_signal_connect_notify (
1063 		attachment, "notify::percent",
1064 		G_CALLBACK (attachment_update_progress_columns), NULL);
1065 
1066 	e_signal_connect_notify (
1067 		attachment, "notify::saving",
1068 		G_CALLBACK (attachment_update_icon_column), NULL);
1069 
1070 	e_signal_connect_notify (
1071 		attachment, "notify::saving",
1072 		G_CALLBACK (attachment_update_progress_columns), NULL);
1073 
1074 	e_signal_connect_notify (
1075 		attachment, "notify::signed",
1076 		G_CALLBACK (attachment_update_icon_column), NULL);
1077 
1078 	g_signal_connect_swapped (
1079 		attachment->priv->cancellable, "cancelled",
1080 		G_CALLBACK (attachment_cancelled_cb), attachment);
1081 }
1082 
1083 EAttachment *
e_attachment_new(void)1084 e_attachment_new (void)
1085 {
1086 	return g_object_new (E_TYPE_ATTACHMENT, NULL);
1087 }
1088 
1089 EAttachment *
e_attachment_new_for_path(const gchar * path)1090 e_attachment_new_for_path (const gchar *path)
1091 {
1092 	EAttachment *attachment;
1093 	GFile *file;
1094 
1095 	g_return_val_if_fail (path != NULL, NULL);
1096 
1097 	file = g_file_new_for_path (path);
1098 	attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL);
1099 	g_object_unref (file);
1100 
1101 	return attachment;
1102 }
1103 
1104 EAttachment *
e_attachment_new_for_uri(const gchar * uri)1105 e_attachment_new_for_uri (const gchar *uri)
1106 {
1107 	EAttachment *attachment;
1108 	GFile *file;
1109 
1110 	g_return_val_if_fail (uri != NULL, NULL);
1111 
1112 	file = g_file_new_for_uri (uri);
1113 	attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL);
1114 	g_object_unref (file);
1115 
1116 	return attachment;
1117 }
1118 
1119 EAttachment *
e_attachment_new_for_message(CamelMimeMessage * message)1120 e_attachment_new_for_message (CamelMimeMessage *message)
1121 {
1122 	CamelDataWrapper *wrapper;
1123 	CamelMimePart *mime_part;
1124 	EAttachment *attachment;
1125 	GString *description;
1126 	const gchar *subject;
1127 
1128 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1129 
1130 	mime_part = camel_mime_part_new ();
1131 	camel_mime_part_set_disposition (mime_part, "inline");
1132 	subject = camel_mime_message_get_subject (message);
1133 
1134 	/* To Translators: This text is set as a description of an attached
1135 	 * message when, for example, attaching it to a composer. When the
1136 	 * message to be attached has also filled Subject, then this text is
1137 	 * of form "Attached message - Subject", otherwise it's left as is. */
1138 	description = g_string_new (_("Attached message"));
1139 	if (subject != NULL)
1140 		g_string_append_printf (description, " - %s", subject);
1141 	camel_mime_part_set_description (mime_part, description->str);
1142 	g_string_free (description, TRUE);
1143 
1144 	wrapper = CAMEL_DATA_WRAPPER (message);
1145 	camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
1146 	camel_mime_part_set_content_type (mime_part, "message/rfc822");
1147 
1148 	attachment = e_attachment_new ();
1149 	e_attachment_set_mime_part (attachment, mime_part);
1150 	g_object_unref (mime_part);
1151 
1152 	return attachment;
1153 }
1154 
1155 void
e_attachment_add_to_multipart(EAttachment * attachment,CamelMultipart * multipart,const gchar * default_charset)1156 e_attachment_add_to_multipart (EAttachment *attachment,
1157                                CamelMultipart *multipart,
1158                                const gchar *default_charset)
1159 {
1160 	CamelContentType *content_type;
1161 	CamelDataWrapper *wrapper;
1162 	CamelMimePart *mime_part;
1163 
1164 	/* XXX EMsgComposer might be a better place for this function. */
1165 
1166 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1167 	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
1168 
1169 	/* Still loading?  Too bad. */
1170 	mime_part = e_attachment_ref_mime_part (attachment);
1171 	if (mime_part == NULL)
1172 		return;
1173 
1174 	content_type = camel_mime_part_get_content_type (mime_part);
1175 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1176 
1177 	if (CAMEL_IS_MULTIPART (wrapper))
1178 		goto exit;
1179 
1180 	/* For text content, determine the best encoding and character set. */
1181 	if (camel_content_type_is (content_type, "text", "*")) {
1182 		CamelTransferEncoding encoding;
1183 		CamelStream *filtered_stream;
1184 		CamelMimeFilter *filter;
1185 		CamelStream *stream;
1186 		const gchar *charset;
1187 
1188 		charset = camel_content_type_param (content_type, "charset");
1189 
1190 		/* Determine the best encoding by writing the MIME
1191 		 * part to a NULL stream with a "bestenc" filter. */
1192 		stream = camel_stream_null_new ();
1193 		filtered_stream = camel_stream_filter_new (stream);
1194 		filter = camel_mime_filter_bestenc_new (
1195 			CAMEL_BESTENC_GET_ENCODING);
1196 		camel_stream_filter_add (
1197 			CAMEL_STREAM_FILTER (filtered_stream),
1198 			CAMEL_MIME_FILTER (filter));
1199 		camel_data_wrapper_decode_to_stream_sync (
1200 			wrapper, filtered_stream, NULL, NULL);
1201 		g_object_unref (filtered_stream);
1202 		g_object_unref (stream);
1203 
1204 		/* Retrieve the best encoding from the filter. */
1205 		encoding = camel_mime_filter_bestenc_get_best_encoding (
1206 			CAMEL_MIME_FILTER_BESTENC (filter),
1207 			CAMEL_BESTENC_8BIT);
1208 		camel_mime_part_set_encoding (mime_part, encoding);
1209 		g_object_unref (filter);
1210 
1211 		if (encoding == CAMEL_TRANSFER_ENCODING_7BIT) {
1212 			/* The text fits within us-ascii, so this is safe.
1213 			 * FIXME Check that this isn't iso-2022-jp? */
1214 			default_charset = "us-ascii";
1215 
1216 		} else if (charset == NULL && default_charset == NULL) {
1217 			default_charset = attachment_get_default_charset ();
1218 			/* FIXME Check that this fits within the
1219 			 *       default_charset and if not, find one
1220 			 *       that does and/or allow the user to
1221 			 *       specify. */
1222 		}
1223 
1224 		if (charset == NULL) {
1225 			gchar *type;
1226 
1227 			camel_content_type_set_param (
1228 				content_type, "charset", default_charset);
1229 			type = camel_content_type_format (content_type);
1230 			camel_mime_part_set_content_type (mime_part, type);
1231 			g_free (type);
1232 		}
1233 
1234 	/* Otherwise, unless it's a message/rfc822, Base64 encode it. */
1235 	} else if (!CAMEL_IS_MIME_MESSAGE (wrapper))
1236 		camel_mime_part_set_encoding (
1237 			mime_part, CAMEL_TRANSFER_ENCODING_BASE64);
1238 
1239 exit:
1240 	camel_multipart_add_part (multipart, mime_part);
1241 
1242 	g_clear_object (&mime_part);
1243 }
1244 
1245 void
e_attachment_cancel(EAttachment * attachment)1246 e_attachment_cancel (EAttachment *attachment)
1247 {
1248 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1249 
1250 	g_cancellable_cancel (attachment->priv->cancellable);
1251 }
1252 
1253 gboolean
e_attachment_is_mail_note(EAttachment * attachment)1254 e_attachment_is_mail_note (EAttachment *attachment)
1255 {
1256 	CamelContentType *ct;
1257 
1258 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1259 
1260 	if (!attachment->priv->mime_part)
1261 		return FALSE;
1262 
1263 	ct = camel_mime_part_get_content_type (attachment->priv->mime_part);
1264 	if (!ct || !camel_content_type_is (ct, "message", "rfc822"))
1265 		return FALSE;
1266 
1267 	return camel_medium_get_header (CAMEL_MEDIUM (attachment->priv->mime_part), "X-Evolution-Note") != NULL;
1268 }
1269 
1270 gboolean
e_attachment_get_can_show(EAttachment * attachment)1271 e_attachment_get_can_show (EAttachment *attachment)
1272 {
1273 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1274 
1275 	return attachment->priv->can_show;
1276 }
1277 
1278 void
e_attachment_set_can_show(EAttachment * attachment,gboolean can_show)1279 e_attachment_set_can_show (EAttachment *attachment,
1280                            gboolean can_show)
1281 {
1282 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1283 
1284 	attachment->priv->can_show = can_show;
1285 
1286 	g_object_notify (G_OBJECT (attachment), "can-show");
1287 }
1288 
1289 const gchar *
e_attachment_get_disposition(EAttachment * attachment)1290 e_attachment_get_disposition (EAttachment *attachment)
1291 {
1292 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1293 
1294 	return attachment->priv->disposition;
1295 }
1296 
1297 gchar *
e_attachment_dup_disposition(EAttachment * attachment)1298 e_attachment_dup_disposition (EAttachment *attachment)
1299 {
1300 	const gchar *protected;
1301 	gchar *duplicate;
1302 
1303 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1304 
1305 	g_mutex_lock (&attachment->priv->property_lock);
1306 
1307 	protected = e_attachment_get_disposition (attachment);
1308 	duplicate = g_strdup (protected);
1309 
1310 	g_mutex_unlock (&attachment->priv->property_lock);
1311 
1312 	return duplicate;
1313 }
1314 
1315 void
e_attachment_set_disposition(EAttachment * attachment,const gchar * disposition)1316 e_attachment_set_disposition (EAttachment *attachment,
1317                               const gchar *disposition)
1318 {
1319 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1320 
1321 	g_mutex_lock (&attachment->priv->property_lock);
1322 
1323 	g_free (attachment->priv->disposition);
1324 	attachment->priv->disposition = g_strdup (disposition);
1325 
1326 	g_mutex_unlock (&attachment->priv->property_lock);
1327 
1328 	g_object_notify (G_OBJECT (attachment), "disposition");
1329 }
1330 
1331 GFile *
e_attachment_ref_file(EAttachment * attachment)1332 e_attachment_ref_file (EAttachment *attachment)
1333 {
1334 	GFile *file = NULL;
1335 
1336 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1337 
1338 	g_mutex_lock (&attachment->priv->property_lock);
1339 
1340 	if (attachment->priv->file != NULL)
1341 		file = g_object_ref (attachment->priv->file);
1342 
1343 	g_mutex_unlock (&attachment->priv->property_lock);
1344 
1345 	return file;
1346 }
1347 
1348 void
e_attachment_set_file(EAttachment * attachment,GFile * file)1349 e_attachment_set_file (EAttachment *attachment,
1350                        GFile *file)
1351 {
1352 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1353 
1354 	if (file != NULL) {
1355 		g_return_if_fail (G_IS_FILE (file));
1356 		g_object_ref (file);
1357 	}
1358 
1359 	g_mutex_lock (&attachment->priv->property_lock);
1360 
1361 	g_clear_object (&attachment->priv->file);
1362 	attachment->priv->file = file;
1363 
1364 	g_mutex_unlock (&attachment->priv->property_lock);
1365 
1366 	g_object_notify (G_OBJECT (attachment), "file");
1367 }
1368 
1369 GFileInfo *
e_attachment_ref_file_info(EAttachment * attachment)1370 e_attachment_ref_file_info (EAttachment *attachment)
1371 {
1372 	GFileInfo *file_info = NULL;
1373 
1374 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1375 
1376 	g_mutex_lock (&attachment->priv->property_lock);
1377 
1378 	if (attachment->priv->file_info != NULL)
1379 		file_info = g_object_ref (attachment->priv->file_info);
1380 
1381 	g_mutex_unlock (&attachment->priv->property_lock);
1382 
1383 	return file_info;
1384 }
1385 
1386 void
e_attachment_set_file_info(EAttachment * attachment,GFileInfo * file_info)1387 e_attachment_set_file_info (EAttachment *attachment,
1388                             GFileInfo *file_info)
1389 {
1390 	GIcon *icon;
1391 
1392 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1393 
1394 	if (file_info != NULL) {
1395 		g_return_if_fail (G_IS_FILE_INFO (file_info));
1396 		g_object_ref (file_info);
1397 	}
1398 
1399 	g_mutex_lock (&attachment->priv->property_lock);
1400 
1401 	g_clear_object (&attachment->priv->file_info);
1402 	attachment->priv->file_info = file_info;
1403 
1404 	/* If the GFileInfo contains a GThemedIcon, append a
1405 	 * fallback icon name to ensure we display something. */
1406 	icon = g_file_info_get_icon (file_info);
1407 	if (G_IS_THEMED_ICON (icon))
1408 		g_themed_icon_append_name (
1409 			G_THEMED_ICON (icon), DEFAULT_ICON_NAME);
1410 
1411 	g_mutex_unlock (&attachment->priv->property_lock);
1412 
1413 	g_object_notify (G_OBJECT (attachment), "file-info");
1414 }
1415 
1416 /**
1417  * e_attachment_dup_mime_type:
1418  * @attachment: an #EAttachment
1419  *
1420  * Returns the MIME type of @attachment according to its #GFileInfo.
1421  * If the @attachment has no #GFileInfo then the function returns %NULL.
1422  * Free the returned MIME type string with g_free().
1423  *
1424  * Returns: a newly-allocated MIME type string, or %NULL
1425  **/
1426 gchar *
e_attachment_dup_mime_type(EAttachment * attachment)1427 e_attachment_dup_mime_type (EAttachment *attachment)
1428 {
1429 	GFileInfo *file_info;
1430 	const gchar *content_type = NULL;
1431 	gchar *mime_type = NULL;
1432 
1433 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1434 
1435 	file_info = e_attachment_ref_file_info (attachment);
1436 	if (file_info != NULL)
1437 		content_type = g_file_info_get_content_type (file_info);
1438 
1439 	if (content_type != NULL)
1440 		mime_type = g_content_type_get_mime_type (content_type);
1441 
1442 	if (mime_type != NULL)
1443 		camel_strdown (mime_type);
1444 
1445 	g_clear_object (&file_info);
1446 
1447 	return mime_type;
1448 }
1449 
1450 GIcon *
e_attachment_ref_icon(EAttachment * attachment)1451 e_attachment_ref_icon (EAttachment *attachment)
1452 {
1453 	GIcon *icon = NULL;
1454 
1455 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1456 
1457 	g_mutex_lock (&attachment->priv->property_lock);
1458 
1459 	if (attachment->priv->icon != NULL)
1460 		icon = g_object_ref (attachment->priv->icon);
1461 
1462 	g_mutex_unlock (&attachment->priv->property_lock);
1463 
1464 	return icon;
1465 }
1466 
1467 gboolean
e_attachment_get_loading(EAttachment * attachment)1468 e_attachment_get_loading (EAttachment *attachment)
1469 {
1470 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1471 
1472 	return attachment->priv->loading;
1473 }
1474 
1475 CamelMimePart *
e_attachment_ref_mime_part(EAttachment * attachment)1476 e_attachment_ref_mime_part (EAttachment *attachment)
1477 {
1478 	CamelMimePart *mime_part = NULL;
1479 
1480 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1481 
1482 	g_mutex_lock (&attachment->priv->property_lock);
1483 
1484 	if (attachment->priv->mime_part != NULL)
1485 		mime_part = g_object_ref (attachment->priv->mime_part);
1486 
1487 	g_mutex_unlock (&attachment->priv->property_lock);
1488 
1489 	return mime_part;
1490 }
1491 
1492 void
e_attachment_set_mime_part(EAttachment * attachment,CamelMimePart * mime_part)1493 e_attachment_set_mime_part (EAttachment *attachment,
1494                             CamelMimePart *mime_part)
1495 {
1496 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1497 
1498 	if (mime_part != NULL) {
1499 		g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
1500 		g_object_ref (mime_part);
1501 	}
1502 
1503 	g_mutex_lock (&attachment->priv->property_lock);
1504 
1505 	g_clear_object (&attachment->priv->mime_part);
1506 	attachment->priv->mime_part = mime_part;
1507 
1508 	g_mutex_unlock (&attachment->priv->property_lock);
1509 
1510 	g_object_notify (G_OBJECT (attachment), "mime-part");
1511 }
1512 
1513 gint
e_attachment_get_percent(EAttachment * attachment)1514 e_attachment_get_percent (EAttachment *attachment)
1515 {
1516 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), 0);
1517 
1518 	return attachment->priv->percent;
1519 }
1520 
1521 gboolean
e_attachment_get_saving(EAttachment * attachment)1522 e_attachment_get_saving (EAttachment *attachment)
1523 {
1524 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1525 
1526 	return attachment->priv->saving;
1527 }
1528 
1529 gboolean
e_attachment_get_initially_shown(EAttachment * attachment)1530 e_attachment_get_initially_shown (EAttachment *attachment)
1531 {
1532 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1533 
1534 	return attachment->priv->initially_shown;
1535 }
1536 
1537 void
e_attachment_set_initially_shown(EAttachment * attachment,gboolean initially_shown)1538 e_attachment_set_initially_shown (EAttachment *attachment,
1539 				  gboolean initially_shown)
1540 {
1541 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1542 
1543 	attachment->priv->initially_shown = initially_shown;
1544 
1545 	g_object_notify (G_OBJECT (attachment), "initially-shown");
1546 }
1547 
1548 gboolean
e_attachment_get_save_self(EAttachment * attachment)1549 e_attachment_get_save_self (EAttachment *attachment)
1550 {
1551 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), TRUE);
1552 
1553 	return attachment->priv->save_self;
1554 }
1555 
1556 void
e_attachment_set_save_self(EAttachment * attachment,gboolean save_self)1557 e_attachment_set_save_self (EAttachment *attachment,
1558                             gboolean save_self)
1559 {
1560 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1561 
1562 	attachment->priv->save_self = save_self;
1563 }
1564 
1565 gboolean
e_attachment_get_save_extracted(EAttachment * attachment)1566 e_attachment_get_save_extracted (EAttachment *attachment)
1567 {
1568 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1569 
1570 	return attachment->priv->save_extracted;
1571 }
1572 
1573 void
e_attachment_set_save_extracted(EAttachment * attachment,gboolean save_extracted)1574 e_attachment_set_save_extracted (EAttachment *attachment,
1575                                  gboolean save_extracted)
1576 {
1577 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1578 
1579 	attachment->priv->save_extracted = save_extracted;
1580 }
1581 
1582 CamelCipherValidityEncrypt
e_attachment_get_encrypted(EAttachment * attachment)1583 e_attachment_get_encrypted (EAttachment *attachment)
1584 {
1585 	g_return_val_if_fail (
1586 		E_IS_ATTACHMENT (attachment),
1587 		CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE);
1588 
1589 	return attachment->priv->encrypted;
1590 }
1591 
1592 void
e_attachment_set_encrypted(EAttachment * attachment,CamelCipherValidityEncrypt encrypted)1593 e_attachment_set_encrypted (EAttachment *attachment,
1594                             CamelCipherValidityEncrypt encrypted)
1595 {
1596 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1597 
1598 	attachment->priv->encrypted = encrypted;
1599 
1600 	g_object_notify (G_OBJECT (attachment), "encrypted");
1601 }
1602 
1603 CamelCipherValiditySign
e_attachment_get_signed(EAttachment * attachment)1604 e_attachment_get_signed (EAttachment *attachment)
1605 {
1606 	g_return_val_if_fail (
1607 		E_IS_ATTACHMENT (attachment),
1608 		CAMEL_CIPHER_VALIDITY_SIGN_NONE);
1609 
1610 	return attachment->priv->signed_;
1611 }
1612 
1613 void
e_attachment_set_signed(EAttachment * attachment,CamelCipherValiditySign signed_)1614 e_attachment_set_signed (EAttachment *attachment,
1615                          CamelCipherValiditySign signed_)
1616 {
1617 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1618 
1619 	attachment->priv->signed_ = signed_;
1620 
1621 	g_object_notify (G_OBJECT (attachment), "signed");
1622 }
1623 
1624 gchar *
e_attachment_dup_description(EAttachment * attachment)1625 e_attachment_dup_description (EAttachment *attachment)
1626 {
1627 	GFileInfo *file_info;
1628 	const gchar *attribute;
1629 	const gchar *protected;
1630 	gchar *duplicate;
1631 
1632 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1633 
1634 	file_info = e_attachment_ref_file_info (attachment);
1635 	if (file_info == NULL)
1636 		return NULL;
1637 
1638 	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
1639 	protected = g_file_info_get_attribute_string (file_info, attribute);
1640 	duplicate = g_strdup (protected);
1641 
1642 	g_object_unref (file_info);
1643 
1644 	return duplicate;
1645 }
1646 
1647 gchar *
e_attachment_dup_thumbnail_path(EAttachment * attachment)1648 e_attachment_dup_thumbnail_path (EAttachment *attachment)
1649 {
1650 	GFileInfo *file_info;
1651 	const gchar *attribute;
1652 	const gchar *protected;
1653 	gchar *duplicate;
1654 
1655 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1656 
1657 	file_info = e_attachment_ref_file_info (attachment);
1658 	if (file_info == NULL)
1659 		return NULL;
1660 
1661 	attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH;
1662 	protected = g_file_info_get_attribute_string (file_info, attribute);
1663 	duplicate = g_strdup (protected);
1664 
1665 	g_object_unref (file_info);
1666 
1667 	return duplicate;
1668 }
1669 
1670 gboolean
e_attachment_is_rfc822(EAttachment * attachment)1671 e_attachment_is_rfc822 (EAttachment *attachment)
1672 {
1673 	gchar *mime_type;
1674 	gboolean is_rfc822;
1675 
1676 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1677 
1678 	mime_type = e_attachment_dup_mime_type (attachment);
1679 	is_rfc822 =
1680 		(mime_type != NULL) &&
1681 		(g_ascii_strcasecmp (mime_type, "message/rfc822") == 0);
1682 	g_free (mime_type);
1683 
1684 	return is_rfc822;
1685 }
1686 
1687 GAppInfo *
e_attachment_ref_default_app(EAttachment * attachment)1688 e_attachment_ref_default_app (EAttachment *attachment)
1689 {
1690 	GFileInfo *file_info;
1691 	GAppInfo *default_app = NULL;
1692 	const gchar *content_type;
1693 
1694 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1695 
1696 	file_info = e_attachment_ref_file_info (attachment);
1697 	if (file_info == NULL)
1698 		return NULL;
1699 
1700 	content_type = g_file_info_get_content_type (file_info);
1701 	if (content_type && !g_content_type_equals (content_type, "application/octet-stream"))
1702 		default_app = g_app_info_get_default_for_type (content_type, FALSE);
1703 
1704 	g_object_unref (file_info);
1705 
1706 	return default_app;
1707 }
1708 
1709 GList *
e_attachment_list_apps(EAttachment * attachment)1710 e_attachment_list_apps (EAttachment *attachment)
1711 {
1712 	GList *app_info_list;
1713 	GList *guessed_infos;
1714 	GFileInfo *file_info;
1715 	GAppInfo *default_app;
1716 	const gchar *content_type;
1717 	const gchar *display_name;
1718 	gboolean type_is_unknown;
1719 	gchar *allocated;
1720 
1721 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1722 
1723 	file_info = e_attachment_ref_file_info (attachment);
1724 	if (file_info == NULL)
1725 		return NULL;
1726 
1727 	content_type = g_file_info_get_content_type (file_info);
1728 	display_name = g_file_info_get_display_name (file_info);
1729 	g_return_val_if_fail (content_type != NULL, NULL);
1730 
1731 	app_info_list = g_app_info_get_all_for_type (content_type);
1732 	type_is_unknown = g_content_type_is_unknown (content_type);
1733 
1734 	if (app_info_list != NULL && !type_is_unknown)
1735 		goto exit;
1736 
1737 	if (display_name == NULL)
1738 		goto exit;
1739 
1740 	allocated = g_content_type_guess (display_name, NULL, 0, NULL);
1741 	guessed_infos = g_app_info_get_all_for_type (allocated);
1742 	app_info_list = g_list_concat (guessed_infos, app_info_list);
1743 	g_free (allocated);
1744 
1745  exit:
1746 	default_app = e_attachment_ref_default_app (attachment);
1747 	if (default_app) {
1748 		GList *link;
1749 
1750 		for (link = app_info_list; link; link = g_list_next (link)) {
1751 			GAppInfo *app_info = link->data;
1752 
1753 			if (g_app_info_equal (default_app, app_info)) {
1754 				if (link != app_info_list) {
1755 					app_info_list = g_list_delete_link (app_info_list, link);
1756 					g_object_unref (app_info);
1757 
1758 					app_info_list = g_list_prepend (app_info_list, default_app);
1759 					default_app = NULL;
1760 				}
1761 				break;
1762 			}
1763 		}
1764 
1765 		g_clear_object (&default_app);
1766 	}
1767 
1768 	g_clear_object (&file_info);
1769 
1770 	return app_info_list;
1771 }
1772 
1773 void
e_attachment_update_store_columns(EAttachment * attachment)1774 e_attachment_update_store_columns (EAttachment *attachment)
1775 {
1776 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
1777 
1778 	attachment_update_file_info_columns (attachment);
1779 	attachment_update_icon_column (attachment);
1780 	attachment_update_progress_columns (attachment);
1781 }
1782 
1783 /************************* e_attachment_load_async() *************************/
1784 
1785 typedef struct _LoadContext LoadContext;
1786 
1787 struct _LoadContext {
1788 	EAttachment *attachment;
1789 	CamelMimePart *mime_part;
1790 	GSimpleAsyncResult *simple;
1791 
1792 	GInputStream *input_stream;
1793 	GOutputStream *output_stream;
1794 	GFileInfo *file_info;
1795 	goffset total_num_bytes;
1796 	gssize bytes_read;
1797 	gchar buffer[4096];
1798 };
1799 
1800 /* Forward Declaration */
1801 static void
1802 attachment_load_stream_read_cb (GInputStream *input_stream,
1803                                 GAsyncResult *result,
1804                                 LoadContext *load_context);
1805 static void
1806 attachment_load_query_info_cb (GFile *file,
1807                                GAsyncResult *result,
1808                                LoadContext *load_context);
1809 
1810 static LoadContext *
attachment_load_context_new(EAttachment * attachment,GAsyncReadyCallback callback,gpointer user_data)1811 attachment_load_context_new (EAttachment *attachment,
1812                              GAsyncReadyCallback callback,
1813                              gpointer user_data)
1814 {
1815 	LoadContext *load_context;
1816 	GSimpleAsyncResult *simple;
1817 
1818 	simple = g_simple_async_result_new (
1819 		G_OBJECT (attachment), callback,
1820 		user_data, e_attachment_load_async);
1821 
1822 	load_context = g_slice_new0 (LoadContext);
1823 	load_context->attachment = g_object_ref (attachment);
1824 	load_context->simple = simple;
1825 
1826 	attachment_set_loading (load_context->attachment, TRUE);
1827 
1828 	return load_context;
1829 }
1830 
1831 static void
attachment_load_context_free(LoadContext * load_context)1832 attachment_load_context_free (LoadContext *load_context)
1833 {
1834 	g_object_unref (load_context->attachment);
1835 
1836 	if (load_context->mime_part != NULL)
1837 		g_object_unref (load_context->mime_part);
1838 
1839 	if (load_context->simple)
1840 		g_object_unref (load_context->simple);
1841 
1842 	if (load_context->input_stream != NULL)
1843 		g_object_unref (load_context->input_stream);
1844 
1845 	if (load_context->output_stream != NULL)
1846 		g_object_unref (load_context->output_stream);
1847 
1848 	if (load_context->file_info != NULL)
1849 		g_object_unref (load_context->file_info);
1850 
1851 	g_slice_free (LoadContext, load_context);
1852 }
1853 
1854 static gboolean
attachment_load_check_for_error(LoadContext * load_context,GError * error)1855 attachment_load_check_for_error (LoadContext *load_context,
1856                                  GError *error)
1857 {
1858 	GSimpleAsyncResult *simple;
1859 
1860 	if (error == NULL)
1861 		return FALSE;
1862 
1863 	simple = load_context->simple;
1864 	g_simple_async_result_take_error (simple, error);
1865 	g_simple_async_result_complete (simple);
1866 
1867 	attachment_load_context_free (load_context);
1868 
1869 	return TRUE;
1870 }
1871 
1872 static void
attachment_load_finish(LoadContext * load_context)1873 attachment_load_finish (LoadContext *load_context)
1874 {
1875 	GFileInfo *file_info;
1876 	EAttachment *attachment;
1877 	GMemoryOutputStream *output_stream;
1878 	GSimpleAsyncResult *simple;
1879 	CamelDataWrapper *wrapper;
1880 	CamelMimePart *mime_part;
1881 	CamelStream *stream;
1882 	const gchar *attribute;
1883 	const gchar *content_type;
1884 	const gchar *display_name;
1885 	const gchar *description;
1886 	const gchar *disposition;
1887 	gchar *mime_type;
1888 	gpointer data;
1889 	gsize size;
1890 
1891 	simple = load_context->simple;
1892 
1893 	file_info = load_context->file_info;
1894 	attachment = load_context->attachment;
1895 	output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
1896 
1897 	if (e_attachment_is_rfc822 (attachment))
1898 		wrapper = (CamelDataWrapper *) camel_mime_message_new ();
1899 	else
1900 		wrapper = camel_data_wrapper_new ();
1901 
1902 	content_type = g_file_info_get_content_type (file_info);
1903 	mime_type = g_content_type_get_mime_type (content_type);
1904 
1905 	data = g_memory_output_stream_get_data (output_stream);
1906 	size = g_memory_output_stream_get_data_size (output_stream);
1907 
1908 	stream = camel_stream_mem_new_with_buffer (data, size);
1909 	camel_data_wrapper_construct_from_stream_sync (
1910 		wrapper, stream, NULL, NULL);
1911 	camel_data_wrapper_set_mime_type (wrapper, mime_type);
1912 	camel_stream_close (stream, NULL, NULL);
1913 	g_object_unref (stream);
1914 
1915 	mime_part = camel_mime_part_new ();
1916 	camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
1917 
1918 	g_object_unref (wrapper);
1919 	g_free (mime_type);
1920 
1921 	display_name = g_file_info_get_display_name (file_info);
1922 	if (display_name != NULL)
1923 		camel_mime_part_set_filename (mime_part, display_name);
1924 
1925 	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
1926 	description = g_file_info_get_attribute_string (file_info, attribute);
1927 	if (description != NULL)
1928 		camel_mime_part_set_description (mime_part, description);
1929 
1930 	disposition = e_attachment_get_disposition (attachment);
1931 	if (disposition != NULL)
1932 		camel_mime_part_set_disposition (mime_part, disposition);
1933 
1934 	/* Correctly report the size of zero length special files. */
1935 	if (g_file_info_get_size (file_info) == 0)
1936 		g_file_info_set_size (file_info, size);
1937 
1938 	load_context->mime_part = mime_part;
1939 
1940 	g_simple_async_result_set_op_res_gpointer (
1941 		simple, load_context,
1942 		(GDestroyNotify) attachment_load_context_free);
1943 
1944 	g_simple_async_result_complete (simple);
1945 
1946 	/* Make sure it's freed on operation end. */
1947 	g_clear_object (&load_context->simple);
1948 }
1949 
1950 static void
attachment_load_write_cb(GOutputStream * output_stream,GAsyncResult * result,LoadContext * load_context)1951 attachment_load_write_cb (GOutputStream *output_stream,
1952                           GAsyncResult *result,
1953                           LoadContext *load_context)
1954 {
1955 	EAttachment *attachment;
1956 	GCancellable *cancellable;
1957 	GInputStream *input_stream;
1958 	gssize bytes_written;
1959 	GError *error = NULL;
1960 
1961 	bytes_written = g_output_stream_write_finish (
1962 		output_stream, result, &error);
1963 
1964 	if (attachment_load_check_for_error (load_context, error))
1965 		return;
1966 
1967 	attachment = load_context->attachment;
1968 	cancellable = attachment->priv->cancellable;
1969 	input_stream = load_context->input_stream;
1970 
1971 	attachment_progress_cb (
1972 		g_seekable_tell (G_SEEKABLE (output_stream)),
1973 		load_context->total_num_bytes, attachment);
1974 
1975 	if (bytes_written < load_context->bytes_read) {
1976 		memmove (
1977 			load_context->buffer,
1978 			load_context->buffer + bytes_written,
1979 			load_context->bytes_read - bytes_written);
1980 		load_context->bytes_read -= bytes_written;
1981 
1982 		g_output_stream_write_async (
1983 			output_stream,
1984 			load_context->buffer,
1985 			load_context->bytes_read,
1986 			G_PRIORITY_DEFAULT, cancellable,
1987 			(GAsyncReadyCallback) attachment_load_write_cb,
1988 			load_context);
1989 	} else
1990 		g_input_stream_read_async (
1991 			input_stream,
1992 			load_context->buffer,
1993 			sizeof (load_context->buffer),
1994 			G_PRIORITY_DEFAULT, cancellable,
1995 			(GAsyncReadyCallback) attachment_load_stream_read_cb,
1996 			load_context);
1997 }
1998 
1999 static void
attachment_load_stream_read_cb(GInputStream * input_stream,GAsyncResult * result,LoadContext * load_context)2000 attachment_load_stream_read_cb (GInputStream *input_stream,
2001                                 GAsyncResult *result,
2002                                 LoadContext *load_context)
2003 {
2004 	EAttachment *attachment;
2005 	GCancellable *cancellable;
2006 	GOutputStream *output_stream;
2007 	gssize bytes_read;
2008 	GError *error = NULL;
2009 
2010 	bytes_read = g_input_stream_read_finish (
2011 		input_stream, result, &error);
2012 
2013 	if (attachment_load_check_for_error (load_context, error))
2014 		return;
2015 
2016 	if (bytes_read == 0) {
2017 		attachment_load_finish (load_context);
2018 		return;
2019 	}
2020 
2021 	attachment = load_context->attachment;
2022 	cancellable = attachment->priv->cancellable;
2023 	output_stream = load_context->output_stream;
2024 	load_context->bytes_read = bytes_read;
2025 
2026 	g_output_stream_write_async (
2027 		output_stream,
2028 		load_context->buffer,
2029 		load_context->bytes_read,
2030 		G_PRIORITY_DEFAULT, cancellable,
2031 		(GAsyncReadyCallback) attachment_load_write_cb,
2032 		load_context);
2033 }
2034 
2035 static void
attachment_load_file_read_cb(GFile * file,GAsyncResult * result,LoadContext * load_context)2036 attachment_load_file_read_cb (GFile *file,
2037                               GAsyncResult *result,
2038                               LoadContext *load_context)
2039 {
2040 	EAttachment *attachment;
2041 	GCancellable *cancellable;
2042 	GFileInputStream *input_stream;
2043 	GOutputStream *output_stream;
2044 	GError *error = NULL;
2045 
2046 	/* Input stream might be NULL, so don't use cast macro. */
2047 	input_stream = g_file_read_finish (file, result, &error);
2048 	load_context->input_stream = (GInputStream *) input_stream;
2049 
2050 	if (attachment_load_check_for_error (load_context, error))
2051 		return;
2052 
2053 	/* Load the contents into a GMemoryOutputStream. */
2054 	output_stream = g_memory_output_stream_new (
2055 		NULL, 0, g_realloc, g_free);
2056 
2057 	attachment = load_context->attachment;
2058 	cancellable = attachment->priv->cancellable;
2059 	load_context->output_stream = output_stream;
2060 
2061 	g_input_stream_read_async (
2062 		load_context->input_stream,
2063 		load_context->buffer,
2064 		sizeof (load_context->buffer),
2065 		G_PRIORITY_DEFAULT, cancellable,
2066 		(GAsyncReadyCallback) attachment_load_stream_read_cb,
2067 		load_context);
2068 }
2069 
2070 #ifdef HAVE_AUTOAR
2071 static void
attachment_load_created_decide_dest_cb(AutoarCompressor * compressor,GFile * destination,EAttachment * attachment)2072 attachment_load_created_decide_dest_cb (AutoarCompressor *compressor,
2073                                         GFile *destination,
2074                                         EAttachment *attachment)
2075 {
2076 	e_attachment_set_file (attachment, destination);
2077 }
2078 
2079 static void
attachment_load_created_cancelled_cb(AutoarCompressor * compressor,LoadContext * load_context)2080 attachment_load_created_cancelled_cb (AutoarCompressor *compressor,
2081                                       LoadContext *load_context)
2082 {
2083 	attachment_load_check_for_error (load_context,
2084 		g_error_new_literal (
2085 			G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")));
2086 	g_object_unref (compressor);
2087 }
2088 
2089 static void
attachment_load_created_completed_cb(AutoarCompressor * compressor,LoadContext * load_context)2090 attachment_load_created_completed_cb (AutoarCompressor *compressor,
2091                                       LoadContext *load_context)
2092 {
2093 	EAttachment *attachment;
2094 	GFile *file;
2095 
2096 	g_object_unref (compressor);
2097 
2098 	/* We have set the file to the created temporary archive, so we can
2099 	 * query info again and use the regular procedure to load the
2100 	 * attachment. */
2101 	attachment = load_context->attachment;
2102 	file = e_attachment_ref_file (attachment);
2103 	g_file_query_info_async (
2104 		file, ATTACHMENT_QUERY,
2105 		G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
2106 		attachment->priv->cancellable, (GAsyncReadyCallback)
2107 		attachment_load_query_info_cb, load_context);
2108 
2109 	g_clear_object (&file);
2110 }
2111 
2112 static void
attachment_load_created_error_cb(AutoarCompressor * compressor,GError * error,LoadContext * load_context)2113 attachment_load_created_error_cb (AutoarCompressor *compressor,
2114                                   GError *error,
2115                                   LoadContext *load_context)
2116 {
2117 	attachment_load_check_for_error (load_context, g_error_copy (error));
2118 	g_object_unref (compressor);
2119 }
2120 #endif
2121 
2122 static void
attachment_load_query_info_cb(GFile * file,GAsyncResult * result,LoadContext * load_context)2123 attachment_load_query_info_cb (GFile *file,
2124                                GAsyncResult *result,
2125                                LoadContext *load_context)
2126 {
2127 	EAttachment *attachment;
2128 	GCancellable *cancellable;
2129 	GFileInfo *file_info;
2130 	GError *error = NULL;
2131 
2132 	attachment = load_context->attachment;
2133 	cancellable = attachment->priv->cancellable;
2134 
2135 	file_info = g_file_query_info_finish (file, result, &error);
2136 	if (attachment_load_check_for_error (load_context, error))
2137 		return;
2138 
2139 	e_attachment_set_file_info (attachment, file_info);
2140 	load_context->file_info = file_info;
2141 
2142 	load_context->total_num_bytes = g_file_info_get_size (file_info);
2143 
2144 #ifdef HAVE_AUTOAR
2145 	if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) {
2146 		AutoarCompressor *compressor;
2147 		GFile *temporary;
2148 		GSettings *settings;
2149 		GList *files = NULL;
2150 		char *format_string;
2151 		char *filter_string;
2152 		gint format;
2153 		gint filter;
2154 
2155 		temporary = attachment_get_temporary (&error);
2156 		if (attachment_load_check_for_error (load_context, error))
2157 			return;
2158 
2159 		settings = e_util_ref_settings ("org.gnome.evolution.shell");
2160 
2161 		format_string = g_settings_get_string (settings, "autoar-format");
2162 		filter_string = g_settings_get_string (settings, "autoar-filter");
2163 
2164 		if (!e_enum_from_string (AUTOAR_TYPE_FORMAT, format_string, &format)) {
2165 			format = AUTOAR_FORMAT_ZIP;
2166 		}
2167 		if (!e_enum_from_string (AUTOAR_TYPE_FILTER, filter_string, &filter)) {
2168 			filter = AUTOAR_FILTER_NONE;
2169 		}
2170 
2171 		files = g_list_prepend (files, file);
2172 
2173 		compressor = autoar_compressor_new (
2174 			files, temporary, format, filter, FALSE);
2175 		g_signal_connect (compressor, "decide-dest",
2176 			G_CALLBACK (attachment_load_created_decide_dest_cb), attachment);
2177 		g_signal_connect (compressor, "cancelled",
2178 			G_CALLBACK (attachment_load_created_cancelled_cb), load_context);
2179 		g_signal_connect (compressor, "completed",
2180 			G_CALLBACK (attachment_load_created_completed_cb), load_context);
2181 		g_signal_connect (compressor, "error",
2182 			G_CALLBACK (attachment_load_created_error_cb), load_context);
2183 		autoar_compressor_start_async (compressor, cancellable);
2184 
2185 		g_object_unref (settings);
2186 		g_free (format_string);
2187 		g_free (filter_string);
2188 		g_list_free (files);
2189 		g_object_unref (temporary);
2190 	} else {
2191 #endif
2192 		g_file_read_async (
2193 			file, G_PRIORITY_DEFAULT,
2194 			cancellable, (GAsyncReadyCallback)
2195 			attachment_load_file_read_cb, load_context);
2196 #ifdef HAVE_AUTOAR
2197 	}
2198 #endif
2199 }
2200 
2201 #define ATTACHMENT_LOAD_CONTEXT "attachment-load-context-data"
2202 
2203 static void
attachment_load_from_mime_part_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)2204 attachment_load_from_mime_part_thread (GSimpleAsyncResult *simple,
2205                                        GObject *object,
2206                                        GCancellable *cancellable)
2207 {
2208 	LoadContext *load_context;
2209 	GFileInfo *file_info;
2210 	EAttachment *attachment;
2211 	CamelContentType *content_type;
2212 	CamelMimePart *mime_part;
2213 	const gchar *attribute;
2214 	const gchar *string;
2215 	gchar *allocated, *decoded_string = NULL;
2216 	gsize bytes_written;
2217 	CamelDataWrapper *dw;
2218 
2219 	load_context = g_object_get_data (
2220 		G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT);
2221 	g_return_if_fail (load_context != NULL);
2222 	g_object_set_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT, NULL);
2223 
2224 	attachment = load_context->attachment;
2225 	mime_part = e_attachment_ref_mime_part (attachment);
2226 
2227 	file_info = g_file_info_new ();
2228 	load_context->file_info = file_info;
2229 
2230 	content_type = camel_mime_part_get_content_type (mime_part);
2231 	allocated = camel_content_type_simple (content_type);
2232 	if (allocated != NULL) {
2233 		GIcon *icon;
2234 		gchar *cp;
2235 
2236 		/* GIO expects lowercase MIME types. */
2237 		for (cp = allocated; *cp != '\0'; cp++)
2238 			*cp = g_ascii_tolower (*cp);
2239 
2240 		/* Swap the MIME type for a content type. */
2241 		cp = g_content_type_from_mime_type (allocated);
2242 		g_free (allocated);
2243 		allocated = cp;
2244 
2245 		/* Use the MIME part's filename if we have to. */
2246 		if (g_content_type_is_unknown (allocated)) {
2247 			string = camel_mime_part_get_filename (mime_part);
2248 			if (string != NULL) {
2249 				g_free (allocated);
2250 				allocated = g_content_type_guess (
2251 					string, NULL, 0, NULL);
2252 			}
2253 		}
2254 
2255 		g_file_info_set_content_type (file_info, allocated);
2256 
2257 		icon = g_content_type_get_icon (allocated);
2258 		if (icon != NULL) {
2259 			g_file_info_set_icon (file_info, icon);
2260 			g_object_unref (icon);
2261 		}
2262 	}
2263 	g_free (allocated);
2264 	allocated = NULL;
2265 
2266 	/* Strip any path components from the filename. */
2267 	string = camel_mime_part_get_filename (mime_part);
2268 	if (string == NULL) {
2269 		/* Translators: Default attachment filename. */
2270 		string = _("attachment.dat");
2271 
2272 		if (camel_content_type_is (content_type, "message", "rfc822")) {
2273 			CamelMimeMessage *msg = NULL;
2274 			const gchar *subject = NULL;
2275 
2276 			if (CAMEL_IS_MIME_MESSAGE (mime_part)) {
2277 				msg = CAMEL_MIME_MESSAGE (mime_part);
2278 			} else {
2279 				CamelDataWrapper *content;
2280 
2281 				content = camel_medium_get_content (
2282 					CAMEL_MEDIUM (mime_part));
2283 				if (CAMEL_IS_MIME_MESSAGE (content))
2284 					msg = CAMEL_MIME_MESSAGE (content);
2285 			}
2286 
2287 			if (msg != NULL)
2288 				subject = camel_mime_message_get_subject (msg);
2289 
2290 			if (subject != NULL && *subject != '\0')
2291 				string = subject;
2292 		}
2293 	} else {
2294 		decoded_string = camel_header_decode_string (string, "UTF-8");
2295 		if (decoded_string != NULL &&
2296 		    *decoded_string != '\0' &&
2297 		    !g_str_equal (decoded_string, string)) {
2298 			string = decoded_string;
2299 		} else {
2300 			g_free (decoded_string);
2301 			decoded_string = NULL;
2302 		}
2303 
2304 		if (string && *string) {
2305 			allocated = g_path_get_basename (string);
2306 			string = allocated;
2307 		}
2308 	}
2309 	g_file_info_set_display_name (file_info, string);
2310 	g_free (decoded_string);
2311 	g_free (allocated);
2312 
2313 	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
2314 	string = camel_mime_part_get_description (mime_part);
2315 	if (string != NULL)
2316 		g_file_info_set_attribute_string (
2317 			file_info, attribute, string);
2318 
2319 	dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2320 	/* this actually downloads the part and makes it available later */
2321 	bytes_written = camel_data_wrapper_calculate_decoded_size_sync (dw, attachment->priv->cancellable, NULL);
2322 	g_file_info_set_size (file_info, bytes_written);
2323 
2324 	load_context->mime_part = g_object_ref (mime_part);
2325 
2326 	/* Make sure it's freed on operation end. */
2327 	g_clear_object (&load_context->simple);
2328 
2329 	g_simple_async_result_set_op_res_gpointer (
2330 		simple, load_context,
2331 		(GDestroyNotify) attachment_load_context_free);
2332 
2333 	g_clear_object (&mime_part);
2334 }
2335 
2336 void
e_attachment_load_async(EAttachment * attachment,GAsyncReadyCallback callback,gpointer user_data)2337 e_attachment_load_async (EAttachment *attachment,
2338                          GAsyncReadyCallback callback,
2339                          gpointer user_data)
2340 {
2341 	LoadContext *load_context;
2342 	GCancellable *cancellable;
2343 	CamelMimePart *mime_part;
2344 	GFile *file;
2345 
2346 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
2347 
2348 	if (e_attachment_get_loading (attachment)) {
2349 		g_simple_async_report_error_in_idle (
2350 			G_OBJECT (attachment), callback, user_data,
2351 			G_IO_ERROR, G_IO_ERROR_BUSY,
2352 			_("A load operation is already in progress"));
2353 		return;
2354 	}
2355 
2356 	if (e_attachment_get_saving (attachment)) {
2357 		g_simple_async_report_error_in_idle (
2358 			G_OBJECT (attachment), callback, user_data,
2359 			G_IO_ERROR, G_IO_ERROR_BUSY,
2360 			_("A save operation is already in progress"));
2361 		return;
2362 	}
2363 
2364 	file = e_attachment_ref_file (attachment);
2365 	mime_part = e_attachment_ref_mime_part (attachment);
2366 	g_return_if_fail (file != NULL || mime_part != NULL);
2367 
2368 	load_context = attachment_load_context_new (
2369 		attachment, callback, user_data);
2370 
2371 	cancellable = attachment->priv->cancellable;
2372 	g_cancellable_reset (cancellable);
2373 
2374 	if (file != NULL) {
2375 		g_file_query_info_async (
2376 			file, ATTACHMENT_QUERY,
2377 			G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
2378 			cancellable, (GAsyncReadyCallback)
2379 			attachment_load_query_info_cb, load_context);
2380 
2381 	} else if (mime_part != NULL) {
2382 		g_object_set_data (
2383 			G_OBJECT (load_context->simple),
2384 			ATTACHMENT_LOAD_CONTEXT, load_context);
2385 
2386 		g_simple_async_result_run_in_thread (
2387 			load_context->simple,
2388 			attachment_load_from_mime_part_thread,
2389 			G_PRIORITY_DEFAULT,
2390 			cancellable);
2391 	}
2392 
2393 	g_clear_object (&file);
2394 	g_clear_object (&mime_part);
2395 }
2396 
2397 gboolean
e_attachment_load_finish(EAttachment * attachment,GAsyncResult * result,GError ** error)2398 e_attachment_load_finish (EAttachment *attachment,
2399                           GAsyncResult *result,
2400                           GError **error)
2401 {
2402 	GSimpleAsyncResult *simple;
2403 	const LoadContext *load_context;
2404 
2405 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2406 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
2407 
2408 	simple = G_SIMPLE_ASYNC_RESULT (result);
2409 	if (g_simple_async_result_propagate_error (simple, error)) {
2410 		attachment_set_loading (attachment, FALSE);
2411 		return FALSE;
2412 	}
2413 
2414 	load_context = g_simple_async_result_get_op_res_gpointer (simple);
2415 
2416 	if (load_context != NULL && load_context->mime_part != NULL) {
2417 		const gchar *string;
2418 
2419 		string = camel_mime_part_get_disposition (
2420 			load_context->mime_part);
2421 		e_attachment_set_disposition (attachment, string);
2422 
2423 		e_attachment_set_file_info (
2424 			attachment, load_context->file_info);
2425 		e_attachment_set_mime_part (
2426 			attachment, load_context->mime_part);
2427 	}
2428 
2429 	attachment_set_loading (attachment, FALSE);
2430 
2431 	return (load_context != NULL);
2432 }
2433 
2434 void
e_attachment_load_handle_error(EAttachment * attachment,GAsyncResult * result,GtkWindow * parent)2435 e_attachment_load_handle_error (EAttachment *attachment,
2436                                 GAsyncResult *result,
2437                                 GtkWindow *parent)
2438 {
2439 	GtkWidget *dialog;
2440 	GFileInfo *file_info;
2441 	const gchar *display_name;
2442 	const gchar *primary_text;
2443 	GError *error = NULL;
2444 
2445 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
2446 	g_return_if_fail (G_IS_ASYNC_RESULT (result));
2447 	g_return_if_fail (!parent || GTK_IS_WINDOW (parent));
2448 
2449 	if (e_attachment_load_finish (attachment, result, &error))
2450 		return;
2451 
2452 	g_signal_emit (attachment, signals[LOAD_FAILED], 0, NULL);
2453 
2454 	/* Ignore cancellations. */
2455 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2456 		g_error_free (error);
2457 		return;
2458 	}
2459 
2460 	file_info = e_attachment_ref_file_info (attachment);
2461 
2462 	if (file_info != NULL)
2463 		display_name = g_file_info_get_display_name (file_info);
2464 	else
2465 		display_name = NULL;
2466 
2467 	if (display_name != NULL)
2468 		primary_text = g_strdup_printf (
2469 			_("Could not load “%s”"), display_name);
2470 	else
2471 		primary_text = g_strdup_printf (
2472 			_("Could not load the attachment"));
2473 
2474 	g_clear_object (&file_info);
2475 
2476 	dialog = gtk_message_dialog_new_with_markup (
2477 		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
2478 		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
2479 		"<big><b>%s</b></big>", primary_text);
2480 
2481 	gtk_message_dialog_format_secondary_text (
2482 		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
2483 
2484 	gtk_dialog_run (GTK_DIALOG (dialog));
2485 
2486 	gtk_widget_destroy (dialog);
2487 	g_error_free (error);
2488 }
2489 
2490 gboolean
e_attachment_load(EAttachment * attachment,GError ** error)2491 e_attachment_load (EAttachment *attachment,
2492                    GError **error)
2493 {
2494 	EAsyncClosure *closure;
2495 	GAsyncResult *result;
2496 	gboolean success;
2497 
2498 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2499 
2500 	closure = e_async_closure_new ();
2501 
2502 	e_attachment_load_async (
2503 		attachment, e_async_closure_callback, closure);
2504 
2505 	result = e_async_closure_wait (closure);
2506 
2507 	success = e_attachment_load_finish (attachment, result, error);
2508 
2509 	e_async_closure_free (closure);
2510 
2511 	return success;
2512 }
2513 
2514 /************************* e_attachment_open_async() *************************/
2515 
2516 typedef struct _OpenContext OpenContext;
2517 
2518 struct _OpenContext {
2519 	EAttachment *attachment;
2520 	GSimpleAsyncResult *simple;
2521 
2522 	GAppInfo *app_info;
2523 };
2524 
2525 static OpenContext *
attachment_open_context_new(EAttachment * attachment,GAsyncReadyCallback callback,gpointer user_data)2526 attachment_open_context_new (EAttachment *attachment,
2527                              GAsyncReadyCallback callback,
2528                              gpointer user_data)
2529 {
2530 	OpenContext *open_context;
2531 	GSimpleAsyncResult *simple;
2532 
2533 	simple = g_simple_async_result_new (
2534 		G_OBJECT (attachment), callback,
2535 		user_data, e_attachment_open_async);
2536 
2537 	open_context = g_slice_new0 (OpenContext);
2538 	open_context->attachment = g_object_ref (attachment);
2539 	open_context->simple = simple;
2540 
2541 	return open_context;
2542 }
2543 
2544 static void
attachment_open_context_free(OpenContext * open_context)2545 attachment_open_context_free (OpenContext *open_context)
2546 {
2547 	g_object_unref (open_context->attachment);
2548 	g_object_unref (open_context->simple);
2549 
2550 	if (open_context->app_info != NULL)
2551 		g_object_unref (open_context->app_info);
2552 
2553 	g_slice_free (OpenContext, open_context);
2554 }
2555 
2556 static gboolean
attachment_open_check_for_error(OpenContext * open_context,GError * error)2557 attachment_open_check_for_error (OpenContext *open_context,
2558                                  GError *error)
2559 {
2560 	GSimpleAsyncResult *simple;
2561 
2562 	if (error == NULL)
2563 		return FALSE;
2564 
2565 	simple = open_context->simple;
2566 	g_simple_async_result_take_error (simple, error);
2567 	g_simple_async_result_complete (simple);
2568 
2569 	attachment_open_context_free (open_context);
2570 
2571 	return TRUE;
2572 }
2573 
2574 static void
attachment_open_file(GFile * file,OpenContext * open_context)2575 attachment_open_file (GFile *file,
2576                       OpenContext *open_context)
2577 {
2578 	GdkAppLaunchContext *context;
2579 	GSimpleAsyncResult *simple;
2580 	GdkDisplay *display;
2581 	gboolean success;
2582 	GError *error = NULL;
2583 
2584 	simple = open_context->simple;
2585 
2586 	display = gdk_display_get_default ();
2587 	context = gdk_display_get_app_launch_context (display);
2588 
2589 	if (open_context->app_info != NULL) {
2590 		GList *file_list;
2591 
2592 		file_list = g_list_prepend (NULL, file);
2593 		success = g_app_info_launch (
2594 			open_context->app_info, file_list,
2595 			G_APP_LAUNCH_CONTEXT (context), &error);
2596 		g_list_free (file_list);
2597 	} else {
2598 		gchar *uri;
2599 
2600 		uri = g_file_get_uri (file);
2601 		success = g_app_info_launch_default_for_uri (
2602 			uri, G_APP_LAUNCH_CONTEXT (context), &error);
2603 		g_free (uri);
2604 	}
2605 
2606 	g_object_unref (context);
2607 
2608 	g_simple_async_result_set_op_res_gboolean (simple, success);
2609 
2610 	if (error != NULL)
2611 		g_simple_async_result_take_error (simple, error);
2612 
2613 	g_simple_async_result_complete (simple);
2614 	attachment_open_context_free (open_context);
2615 }
2616 
2617 static void
attachment_open_save_finished_cb(EAttachment * attachment,GAsyncResult * result,OpenContext * open_context)2618 attachment_open_save_finished_cb (EAttachment *attachment,
2619                                   GAsyncResult *result,
2620                                   OpenContext *open_context)
2621 {
2622 	GFile *file;
2623 	gchar *path;
2624 	GError *error = NULL;
2625 
2626 	file = e_attachment_save_finish (attachment, result, &error);
2627 
2628 	if (attachment_open_check_for_error (open_context, error))
2629 		return;
2630 
2631 	/* Make the temporary file read-only.
2632 	 *
2633 	 * This step is non-critical, so if an error occurs just
2634 	 * emit a warning and move on.
2635 	 *
2636 	 * XXX I haven't figured out how to do this through GIO.
2637 	 *     Attempting to set the "access::can-write" attribute via
2638 	 *     g_file_set_attribute() returned G_IO_ERROR_NOT_SUPPORTED
2639 	 *     and the only other possibility I see is "unix::mode",
2640 	 *     which is obviously not portable.
2641 	 */
2642 	path = g_file_get_path (file);
2643 #ifndef G_OS_WIN32
2644 	if (g_chmod (path, S_IRUSR | S_IRGRP | S_IROTH) < 0)
2645 		g_warning ("%s", g_strerror (errno));
2646 #endif
2647 	g_free (path);
2648 
2649 	attachment_open_file (file, open_context);
2650 	g_object_unref (file);
2651 }
2652 
2653 static void
attachment_open_save_temporary(OpenContext * open_context)2654 attachment_open_save_temporary (OpenContext *open_context)
2655 {
2656 	GFile *temp_directory;
2657 	GError *error = NULL;
2658 
2659 	temp_directory = attachment_get_temporary (&error);
2660 
2661 	/* We already know if there's an error, but this does the cleanup. */
2662 	if (attachment_open_check_for_error (open_context, error))
2663 		return;
2664 
2665 	e_attachment_save_async (
2666 		open_context->attachment,
2667 		temp_directory, (GAsyncReadyCallback)
2668 		attachment_open_save_finished_cb, open_context);
2669 
2670 	g_object_unref (temp_directory);
2671 }
2672 
2673 void
e_attachment_open_async(EAttachment * attachment,GAppInfo * app_info,GAsyncReadyCallback callback,gpointer user_data)2674 e_attachment_open_async (EAttachment *attachment,
2675                          GAppInfo *app_info,
2676                          GAsyncReadyCallback callback,
2677                          gpointer user_data)
2678 {
2679 	OpenContext *open_context;
2680 	CamelMimePart *mime_part;
2681 	GFile *file;
2682 
2683 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
2684 
2685 	file = e_attachment_ref_file (attachment);
2686 	mime_part = e_attachment_ref_mime_part (attachment);
2687 	g_return_if_fail (file != NULL || mime_part != NULL);
2688 
2689 	open_context = attachment_open_context_new (
2690 		attachment, callback, user_data);
2691 
2692 	if (G_IS_APP_INFO (app_info))
2693 		open_context->app_info = g_object_ref (app_info);
2694 
2695 	/* If the attachment already references a GFile, we can launch
2696 	 * the application directly.  Otherwise we have to save the MIME
2697 	 * part to a temporary file and launch the application from that. */
2698 	if (file != NULL) {
2699 		attachment_open_file (file, open_context);
2700 
2701 	} else if (mime_part != NULL)
2702 		attachment_open_save_temporary (open_context);
2703 
2704 	g_clear_object (&file);
2705 	g_clear_object (&mime_part);
2706 }
2707 
2708 gboolean
e_attachment_open_finish(EAttachment * attachment,GAsyncResult * result,GError ** error)2709 e_attachment_open_finish (EAttachment *attachment,
2710                           GAsyncResult *result,
2711                           GError **error)
2712 {
2713 	GSimpleAsyncResult *simple;
2714 	gboolean success;
2715 
2716 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2717 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
2718 
2719 	simple = G_SIMPLE_ASYNC_RESULT (result);
2720 	success = !g_simple_async_result_propagate_error (simple, error) &&
2721 		   g_simple_async_result_get_op_res_gboolean (simple);
2722 
2723 	return success;
2724 }
2725 
2726 void
e_attachment_open_handle_error(EAttachment * attachment,GAsyncResult * result,GtkWindow * parent)2727 e_attachment_open_handle_error (EAttachment *attachment,
2728                                 GAsyncResult *result,
2729                                 GtkWindow *parent)
2730 {
2731 	GtkWidget *dialog;
2732 	GFileInfo *file_info;
2733 	const gchar *display_name;
2734 	const gchar *primary_text;
2735 	GError *error = NULL;
2736 
2737 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
2738 	g_return_if_fail (G_IS_ASYNC_RESULT (result));
2739 	g_return_if_fail (GTK_IS_WINDOW (parent));
2740 
2741 	if (e_attachment_open_finish (attachment, result, &error))
2742 		return;
2743 
2744 	/* Ignore cancellations. */
2745 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2746 		return;
2747 
2748 	file_info = e_attachment_ref_file_info (attachment);
2749 
2750 	if (file_info != NULL)
2751 		display_name = g_file_info_get_display_name (file_info);
2752 	else
2753 		display_name = NULL;
2754 
2755 	if (display_name != NULL)
2756 		primary_text = g_strdup_printf (
2757 			_("Could not open “%s”"), display_name);
2758 	else
2759 		primary_text = g_strdup_printf (
2760 			_("Could not open the attachment"));
2761 
2762 	g_clear_object (&file_info);
2763 
2764 	dialog = gtk_message_dialog_new_with_markup (
2765 		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
2766 		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
2767 		"<big><b>%s</b></big>", primary_text);
2768 
2769 	gtk_message_dialog_format_secondary_text (
2770 		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
2771 
2772 	gtk_dialog_run (GTK_DIALOG (dialog));
2773 
2774 	gtk_widget_destroy (dialog);
2775 	g_error_free (error);
2776 }
2777 
2778 gboolean
e_attachment_open(EAttachment * attachment,GAppInfo * app_info,GError ** error)2779 e_attachment_open (EAttachment *attachment,
2780                    GAppInfo *app_info,
2781                    GError **error)
2782 {
2783 	EAsyncClosure *closure;
2784 	GAsyncResult *result;
2785 	gboolean success;
2786 
2787 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2788 
2789 	closure = e_async_closure_new ();
2790 
2791 	e_attachment_open_async (
2792 		attachment, app_info,
2793 		e_async_closure_callback, closure);
2794 
2795 	result = e_async_closure_wait (closure);
2796 
2797 	success = e_attachment_open_finish (attachment, result, error);
2798 
2799 	e_async_closure_free (closure);
2800 
2801 	return success;
2802 }
2803 
2804 /************************* e_attachment_save_async() *************************/
2805 
2806 typedef struct _SaveContext SaveContext;
2807 
2808 struct _SaveContext {
2809 	EAttachment *attachment;
2810 	GSimpleAsyncResult *simple;
2811 
2812 	GFile *directory;
2813 	GFile *destination;
2814 	GInputStream *input_stream;
2815 	GOutputStream *output_stream;
2816 	goffset total_num_bytes;
2817 	gssize bytes_read;
2818 	gchar buffer[4096];
2819 	gint count;
2820 
2821 	GByteArray *input_buffer;
2822 	gchar *suggested_destname;
2823 	GFile *temporary_file;
2824 
2825 	guint total_tasks : 2;
2826 	guint completed_tasks : 2;
2827 	guint prepared_tasks : 2;
2828 
2829 	GMutex completed_tasks_mutex;
2830 	GMutex prepared_tasks_mutex;
2831 };
2832 
2833 /* Forward Declaration */
2834 static void
2835 attachment_save_read_cb (GInputStream *input_stream,
2836                          GAsyncResult *result,
2837                          SaveContext *save_context);
2838 
2839 static SaveContext *
attachment_save_context_new(EAttachment * attachment,GAsyncReadyCallback callback,gpointer user_data)2840 attachment_save_context_new (EAttachment *attachment,
2841                              GAsyncReadyCallback callback,
2842                              gpointer user_data)
2843 {
2844 	SaveContext *save_context;
2845 	GSimpleAsyncResult *simple;
2846 
2847 	simple = g_simple_async_result_new (
2848 		G_OBJECT (attachment), callback,
2849 		user_data, e_attachment_save_async);
2850 
2851 	save_context = g_slice_new0 (SaveContext);
2852 	save_context->attachment = g_object_ref (attachment);
2853 	save_context->simple = simple;
2854 
2855 	g_mutex_init (&(save_context->completed_tasks_mutex));
2856 	g_mutex_init (&(save_context->prepared_tasks_mutex));
2857 
2858 	attachment_set_saving (save_context->attachment, TRUE);
2859 
2860 	return save_context;
2861 }
2862 
2863 static void
attachment_save_context_free(SaveContext * save_context)2864 attachment_save_context_free (SaveContext *save_context)
2865 {
2866 	g_object_unref (save_context->attachment);
2867 	g_object_unref (save_context->simple);
2868 	g_clear_object (&save_context->directory);
2869 	g_clear_object (&save_context->destination);
2870 	g_clear_object (&save_context->input_stream);
2871 	g_clear_object (&save_context->output_stream);
2872 	g_clear_pointer (&save_context->input_buffer, g_byte_array_unref);
2873 	g_free (save_context->suggested_destname);
2874 	g_clear_object (&save_context->temporary_file);
2875 	g_mutex_clear (&(save_context->completed_tasks_mutex));
2876 	g_mutex_clear (&(save_context->prepared_tasks_mutex));
2877 	g_slice_free (SaveContext, save_context);
2878 }
2879 
2880 static gboolean
attachment_save_check_for_error(SaveContext * save_context,GError * error)2881 attachment_save_check_for_error (SaveContext *save_context,
2882                                  GError *error)
2883 {
2884 	GSimpleAsyncResult *simple;
2885 
2886 	if (error == NULL)
2887 		return FALSE;
2888 
2889 	simple = save_context->simple;
2890 	g_simple_async_result_take_error (simple, error);
2891 
2892 	g_mutex_lock (&(save_context->completed_tasks_mutex));
2893 	if (++save_context->completed_tasks >= save_context->total_tasks) {
2894 		g_simple_async_result_complete (simple);
2895 		g_mutex_unlock (&(save_context->completed_tasks_mutex));
2896 		attachment_save_context_free (save_context);
2897 	} else {
2898 		g_mutex_unlock (&(save_context->completed_tasks_mutex));
2899 	}
2900 
2901 	return TRUE;
2902 }
2903 
2904 static void
attachment_save_complete(SaveContext * save_context)2905 attachment_save_complete (SaveContext *save_context) {
2906 	g_mutex_lock (&(save_context->completed_tasks_mutex));
2907 	if (++save_context->completed_tasks >= save_context->total_tasks) {
2908 		GSimpleAsyncResult *simple;
2909 		GFile *result;
2910 
2911 		/* Steal the destination. */
2912 		result = save_context->destination;
2913 		save_context->destination = NULL;
2914 
2915 		if (result == NULL) {
2916 			result = save_context->directory;
2917 			save_context->directory = NULL;
2918 		}
2919 
2920 		simple = save_context->simple;
2921 		g_simple_async_result_set_op_res_gpointer (
2922 			simple, result, (GDestroyNotify) g_object_unref);
2923 		g_simple_async_result_complete (simple);
2924 		g_mutex_unlock (&(save_context->completed_tasks_mutex));
2925 		attachment_save_context_free (save_context);
2926 	} else {
2927 		g_mutex_unlock (&(save_context->completed_tasks_mutex));
2928 	}
2929 }
2930 
2931 static gchar *
get_new_name_with_count(const gchar * initial_name,gint count)2932 get_new_name_with_count (const gchar *initial_name,
2933                          gint count)
2934 {
2935 	GString *string;
2936 	const gchar *ext;
2937 	gsize length;
2938 
2939 	if (count == 0) {
2940 		return g_strdup (initial_name);
2941 	}
2942 
2943 	string = g_string_sized_new (strlen (initial_name));
2944 	ext = g_utf8_strchr (initial_name, -1, '.');
2945 
2946 	if (ext != NULL)
2947 		length = ext - initial_name;
2948 	else
2949 		length = strlen (initial_name);
2950 
2951 	g_string_append_len (string, initial_name, length);
2952 	g_string_append_printf (string, " (%d)", count);
2953 	g_string_append (string, ext ? ext : "");
2954 
2955 	return g_string_free (string, FALSE);
2956 }
2957 
2958 static GFile *
attachment_save_new_candidate(SaveContext * save_context)2959 attachment_save_new_candidate (SaveContext *save_context)
2960 {
2961 	GFile *candidate;
2962 	GFileInfo *file_info;
2963 	EAttachment *attachment;
2964 	const gchar *display_name = NULL;
2965 	gchar *basename, *allocated;
2966 
2967 	attachment = save_context->attachment;
2968 	file_info = e_attachment_ref_file_info (attachment);
2969 
2970 	if (file_info != NULL)
2971 		display_name = g_file_info_get_display_name (file_info);
2972 	if (display_name == NULL)
2973 		/* Translators: Default attachment filename. */
2974 		display_name = _("attachment.dat");
2975 
2976 	allocated = g_strdup (display_name);
2977 	e_util_make_safe_filename (allocated);
2978 
2979 	basename = get_new_name_with_count (allocated, save_context->count);
2980 
2981 	save_context->count++;
2982 
2983 	candidate = g_file_get_child (save_context->directory, basename);
2984 
2985 	g_free (allocated);
2986 	g_free (basename);
2987 
2988 	g_clear_object (&file_info);
2989 
2990 	return candidate;
2991 }
2992 
2993 static void
attachment_save_write_cb(GOutputStream * output_stream,GAsyncResult * result,SaveContext * save_context)2994 attachment_save_write_cb (GOutputStream *output_stream,
2995                           GAsyncResult *result,
2996                           SaveContext *save_context)
2997 {
2998 	EAttachment *attachment;
2999 	GCancellable *cancellable;
3000 	GInputStream *input_stream;
3001 	gssize bytes_written;
3002 	GError *error = NULL;
3003 
3004 	bytes_written = g_output_stream_write_finish (
3005 		output_stream, result, &error);
3006 
3007 	if (attachment_save_check_for_error (save_context, error))
3008 		return;
3009 
3010 	attachment = save_context->attachment;
3011 	cancellable = attachment->priv->cancellable;
3012 	input_stream = save_context->input_stream;
3013 
3014 	if (bytes_written < save_context->bytes_read) {
3015 		memmove (
3016 			save_context->buffer,
3017 			save_context->buffer + bytes_written,
3018 			save_context->bytes_read - bytes_written);
3019 		save_context->bytes_read -= bytes_written;
3020 
3021 		g_output_stream_write_async (
3022 			output_stream,
3023 			save_context->buffer,
3024 			save_context->bytes_read,
3025 			G_PRIORITY_DEFAULT, cancellable,
3026 			(GAsyncReadyCallback) attachment_save_write_cb,
3027 			save_context);
3028 	} else
3029 		g_input_stream_read_async (
3030 			input_stream,
3031 			save_context->buffer,
3032 			sizeof (save_context->buffer),
3033 			G_PRIORITY_DEFAULT, cancellable,
3034 			(GAsyncReadyCallback) attachment_save_read_cb,
3035 			save_context);
3036 }
3037 
3038 static void
attachment_save_read_cb(GInputStream * input_stream,GAsyncResult * result,SaveContext * save_context)3039 attachment_save_read_cb (GInputStream *input_stream,
3040                          GAsyncResult *result,
3041                          SaveContext *save_context)
3042 {
3043 	EAttachment *attachment;
3044 	GCancellable *cancellable;
3045 	GOutputStream *output_stream;
3046 	gssize bytes_read;
3047 	GError *error = NULL;
3048 
3049 	bytes_read = g_input_stream_read_finish (
3050 		input_stream, result, &error);
3051 
3052 	if (attachment_save_check_for_error (save_context, error))
3053 		return;
3054 
3055 	if (bytes_read == 0) {
3056 		attachment_save_complete (save_context);
3057 		return;
3058 	}
3059 
3060 	attachment = save_context->attachment;
3061 	cancellable = attachment->priv->cancellable;
3062 	output_stream = save_context->output_stream;
3063 	save_context->bytes_read = bytes_read;
3064 
3065 	attachment_progress_cb (
3066 		g_seekable_tell (G_SEEKABLE (input_stream)),
3067 		save_context->total_num_bytes, attachment);
3068 
3069 	g_output_stream_write_async (
3070 		output_stream,
3071 		save_context->buffer,
3072 		save_context->bytes_read,
3073 		G_PRIORITY_DEFAULT, cancellable,
3074 		(GAsyncReadyCallback) attachment_save_write_cb,
3075 		save_context);
3076 }
3077 
3078 #ifdef HAVE_AUTOAR
3079 static GFile*
attachment_save_extracted_decide_destination_cb(AutoarExtractor * extractor,GFile * destination,GList * files,SaveContext * save_context)3080 attachment_save_extracted_decide_destination_cb (AutoarExtractor *extractor,
3081                                                  GFile *destination,
3082                                                  GList *files,
3083                                                  SaveContext *save_context)
3084 {
3085 	gchar *basename;
3086 	GFile *destination_directory;
3087 	GFile *new_destination;
3088 	gint count = 0;
3089 
3090 	basename = g_file_get_basename (destination);
3091 	destination_directory = g_file_get_parent (destination);
3092 
3093 	new_destination = g_object_ref (destination);
3094 
3095 	while (g_file_query_exists (new_destination, NULL)) {
3096 		gchar *new_basename;
3097 
3098 		new_basename = get_new_name_with_count (basename, ++count);
3099 
3100 		g_object_unref (new_destination);
3101 
3102 		new_destination = g_file_get_child (
3103 			destination_directory, new_basename);
3104 
3105 		g_free (new_basename);
3106 	}
3107 
3108 	g_object_unref (destination_directory);
3109 	g_free (basename);
3110 
3111 	return new_destination;
3112 }
3113 
3114 static void
attachment_save_extracted_progress_cb(AutoarExtractor * extractor,guint64 completed_size,guint completed_files,SaveContext * save_context)3115 attachment_save_extracted_progress_cb (AutoarExtractor *extractor,
3116                                        guint64 completed_size,
3117                                        guint completed_files,
3118                                        SaveContext *save_context)
3119 {
3120 	attachment_progress_cb (
3121 		autoar_extractor_get_total_size (extractor),
3122 		completed_size, save_context->attachment);
3123 }
3124 
3125 static void
attachment_save_extracted_cancelled_cb(AutoarExtractor * extractor,SaveContext * save_context)3126 attachment_save_extracted_cancelled_cb (AutoarExtractor *extractor,
3127                                         SaveContext *save_context)
3128 {
3129 	if (attachment_save_check_for_error (save_context,
3130 		g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")))) {
3131 		;
3132 	}
3133 
3134 	g_object_unref (extractor);
3135 }
3136 
3137 static void
attachment_save_extracted_completed_cb(AutoarExtractor * extractor,SaveContext * save_context)3138 attachment_save_extracted_completed_cb (AutoarExtractor *extractor,
3139                                         SaveContext *save_context)
3140 {
3141 	attachment_save_complete (save_context);
3142 	g_object_unref (extractor);
3143 }
3144 
3145 static void
attachment_save_extracted_error_cb(AutoarExtractor * extractor,GError * error,SaveContext * save_context)3146 attachment_save_extracted_error_cb (AutoarExtractor *extractor,
3147                                     GError *error,
3148                                     SaveContext *save_context)
3149 {
3150 	if (attachment_save_check_for_error (save_context, g_error_copy (error))) {
3151 		;
3152 	}
3153 
3154 	g_object_unref (extractor);
3155 }
3156 
3157 static void
attachment_save_write_archive_cb(GOutputStream * output_stream,GAsyncResult * result,SaveContext * save_context)3158 attachment_save_write_archive_cb (GOutputStream *output_stream,
3159                                    GAsyncResult *result,
3160                                    SaveContext *save_context)
3161 {
3162 	AutoarExtractor *extractor;
3163 	GError *error = NULL;
3164 	gsize bytes_written;
3165 
3166 	g_output_stream_write_all_finish (
3167 		output_stream, result, &bytes_written, &error);
3168 
3169 	g_object_unref (output_stream);
3170 
3171 	if (attachment_save_check_for_error (save_context, error)) {
3172 		return;
3173 	}
3174 
3175 	extractor = autoar_extractor_new (
3176 		save_context->temporary_file, save_context->directory);
3177 
3178 	autoar_extractor_set_delete_after_extraction (extractor, TRUE);
3179 
3180 	g_signal_connect (extractor, "decide-destination",
3181 		G_CALLBACK (attachment_save_extracted_decide_destination_cb),
3182 		save_context);
3183 	g_signal_connect (extractor, "progress",
3184 		G_CALLBACK (attachment_save_extracted_progress_cb),
3185 		save_context);
3186 	g_signal_connect (extractor, "cancelled",
3187 		G_CALLBACK (attachment_save_extracted_cancelled_cb),
3188 		save_context);
3189 	g_signal_connect (extractor, "error",
3190 		G_CALLBACK (attachment_save_extracted_error_cb),
3191 		save_context);
3192 	g_signal_connect (extractor, "completed",
3193 		G_CALLBACK (attachment_save_extracted_completed_cb),
3194 		save_context);
3195 
3196 	autoar_extractor_start_async (
3197 		extractor, save_context->attachment->priv->cancellable);
3198 
3199 	/* We do not g_object_unref (extractor); here because
3200 	 * autoar_extractor_run_start_async () does not increase the
3201 	 * reference count of extractor. We unref the object in
3202 	 * callbacks instead. */
3203 }
3204 
3205 static void
attachment_save_create_archive_cb(GFile * file,GAsyncResult * result,SaveContext * save_context)3206 attachment_save_create_archive_cb (GFile *file,
3207                                    GAsyncResult *result,
3208                                    SaveContext *save_context)
3209 {
3210 	GFileOutputStream *output_stream;
3211 	GError *error = NULL;
3212 
3213 	output_stream = g_file_create_finish (file, result, &error);
3214 
3215 	if (attachment_save_check_for_error (save_context, error)) {
3216 		return;
3217 	}
3218 
3219 	g_output_stream_write_all_async (
3220 		G_OUTPUT_STREAM (output_stream),
3221 		save_context->input_buffer->data,
3222 		save_context->input_buffer->len,
3223 		G_PRIORITY_DEFAULT,
3224 		save_context->attachment->priv->cancellable,
3225 		(GAsyncReadyCallback) attachment_save_write_archive_cb,
3226 		save_context);
3227 }
3228 
3229 #endif
3230 
3231 static void
attachment_save_got_output_stream(SaveContext * save_context)3232 attachment_save_got_output_stream (SaveContext *save_context)
3233 {
3234 	GCancellable *cancellable;
3235 	GInputStream *input_stream;
3236 	CamelDataWrapper *wrapper;
3237 	CamelMimePart *mime_part;
3238 	CamelStream *stream;
3239 	EAttachment *attachment;
3240 	GByteArray *buffer;
3241 
3242 	attachment = save_context->attachment;
3243 	cancellable = attachment->priv->cancellable;
3244 	mime_part = e_attachment_ref_mime_part (attachment);
3245 
3246 	/* Decode the MIME part to an in-memory buffer.  We have to do
3247 	 * this because CamelStream is synchronous-only, and using threads
3248 	 * is dangerous because CamelDataWrapper is not reentrant. */
3249 	buffer = g_byte_array_new ();
3250 	stream = camel_stream_mem_new ();
3251 	camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer);
3252 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3253 	camel_data_wrapper_decode_to_stream_sync (wrapper, stream, NULL, NULL);
3254 	g_object_unref (stream);
3255 
3256 	save_context->input_buffer = buffer;
3257 
3258 	if (attachment->priv->save_self) {
3259 		/* Load the buffer into a GMemoryInputStream.
3260 		 * But watch out for zero length MIME parts. */
3261 		input_stream = g_memory_input_stream_new ();
3262 		if (buffer->len > 0)
3263 			g_memory_input_stream_add_data (
3264 				G_MEMORY_INPUT_STREAM (input_stream),
3265 				buffer->data, (gssize) buffer->len, NULL);
3266 		save_context->input_stream = input_stream;
3267 		save_context->total_num_bytes = (goffset) buffer->len;
3268 
3269 		g_input_stream_read_async (
3270 			input_stream,
3271 			save_context->buffer,
3272 			sizeof (save_context->buffer),
3273 			G_PRIORITY_DEFAULT, cancellable,
3274 			(GAsyncReadyCallback) attachment_save_read_cb,
3275 			save_context);
3276 	}
3277 
3278 #ifdef HAVE_AUTOAR
3279 	if (attachment->priv->save_extracted) {
3280 		GFile *temporary_directory;
3281 		GError *error = NULL;
3282 
3283 		temporary_directory = attachment_get_temporary (&error);
3284 		if (attachment_save_check_for_error (save_context, error))
3285 			return;
3286 
3287 		save_context->temporary_file = g_file_get_child (
3288 			temporary_directory, save_context->suggested_destname);
3289 
3290 		g_file_create_async (
3291 			save_context->temporary_file,
3292 			G_FILE_CREATE_NONE,
3293 			G_PRIORITY_DEFAULT,
3294 			cancellable,
3295 			(GAsyncReadyCallback) attachment_save_create_archive_cb,
3296 			save_context);
3297 
3298 		g_object_unref (temporary_directory);
3299 	}
3300 #endif
3301 
3302 	g_clear_object (&mime_part);
3303 }
3304 
3305 static void
attachment_save_create_cb(GFile * destination,GAsyncResult * result,SaveContext * save_context)3306 attachment_save_create_cb (GFile *destination,
3307                            GAsyncResult *result,
3308                            SaveContext *save_context)
3309 {
3310 	EAttachment *attachment;
3311 	GCancellable *cancellable;
3312 	GFileOutputStream *output_stream;
3313 	GError *error = NULL;
3314 
3315 	/* Output stream might be NULL, so don't use cast macro. */
3316 	output_stream = g_file_create_finish (destination, result, &error);
3317 	save_context->output_stream = (GOutputStream *) output_stream;
3318 
3319 	attachment = save_context->attachment;
3320 	cancellable = attachment->priv->cancellable;
3321 
3322 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
3323 		destination = attachment_save_new_candidate (save_context);
3324 
3325 		g_file_create_async (
3326 			destination, G_FILE_CREATE_NONE,
3327 			G_PRIORITY_DEFAULT, cancellable,
3328 			(GAsyncReadyCallback) attachment_save_create_cb,
3329 			save_context);
3330 
3331 		g_object_unref (destination);
3332 		g_error_free (error);
3333 		return;
3334 	}
3335 
3336 	if (attachment_save_check_for_error (save_context, error))
3337 		return;
3338 
3339 	save_context->destination = g_object_ref (destination);
3340 
3341 	g_mutex_lock (&(save_context->prepared_tasks_mutex));
3342 	if (++save_context->prepared_tasks >= save_context->total_tasks)
3343 		attachment_save_got_output_stream (save_context);
3344 	g_mutex_unlock (&(save_context->prepared_tasks_mutex));
3345 }
3346 
3347 static void
attachment_save_replace_cb(GFile * destination,GAsyncResult * result,SaveContext * save_context)3348 attachment_save_replace_cb (GFile *destination,
3349                             GAsyncResult *result,
3350                             SaveContext *save_context)
3351 {
3352 	GFileOutputStream *output_stream;
3353 	GError *error = NULL;
3354 
3355 	/* Output stream might be NULL, so don't use cast macro. */
3356 	output_stream = g_file_replace_finish (destination, result, &error);
3357 	save_context->output_stream = (GOutputStream *) output_stream;
3358 
3359 	if (attachment_save_check_for_error (save_context, error))
3360 		return;
3361 
3362 	save_context->destination = g_object_ref (destination);
3363 
3364 	g_mutex_lock (&(save_context->prepared_tasks_mutex));
3365 	if (++save_context->prepared_tasks >= save_context->total_tasks)
3366 		attachment_save_got_output_stream (save_context);
3367 	g_mutex_unlock (&(save_context->prepared_tasks_mutex));
3368 }
3369 
3370 static void
attachment_save_query_info_cb(GFile * destination,GAsyncResult * result,SaveContext * save_context)3371 attachment_save_query_info_cb (GFile *destination,
3372                                GAsyncResult *result,
3373                                SaveContext *save_context)
3374 {
3375 	EAttachment *attachment;
3376 	GCancellable *cancellable;
3377 	GFileInfo *file_info;
3378 	GFileType file_type;
3379 	GError *error = NULL;
3380 
3381 	attachment = save_context->attachment;
3382 	cancellable = attachment->priv->cancellable;
3383 
3384 	file_info = g_file_query_info_finish (destination, result, &error);
3385 
3386 	/* G_IO_ERROR_NOT_FOUND just means we're creating a new file. */
3387 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
3388 		g_error_free (error);
3389 		goto replace;
3390 	}
3391 
3392 	if (attachment_save_check_for_error (save_context, error))
3393 		return;
3394 
3395 	file_type = g_file_info_get_file_type (file_info);
3396 	g_object_unref (file_info);
3397 
3398 	if (file_type == G_FILE_TYPE_DIRECTORY) {
3399 		save_context->directory = g_object_ref (destination);
3400 
3401 		if (attachment->priv->save_self) {
3402 			destination = attachment_save_new_candidate (save_context);
3403 
3404 			g_file_create_async (
3405 				destination, G_FILE_CREATE_NONE,
3406 				G_PRIORITY_DEFAULT, cancellable,
3407 				(GAsyncReadyCallback) attachment_save_create_cb,
3408 				save_context);
3409 
3410 			g_object_unref (destination);
3411 		}
3412 
3413 #ifdef HAVE_AUTOAR
3414 		if (attachment->priv->save_extracted) {
3415 			EAttachment *attachment;
3416 			GFileInfo *info;
3417 			gchar *suggested;
3418 
3419 			attachment = save_context->attachment;
3420 			suggested = NULL;
3421 			info = e_attachment_ref_file_info (attachment);
3422 			if (info != NULL)
3423 				suggested = g_strdup (
3424 					g_file_info_get_display_name (info));
3425 			if (suggested == NULL)
3426 				suggested = g_strdup (_("attachment.dat"));
3427 
3428 			save_context->suggested_destname = suggested;
3429 
3430 			g_mutex_lock (&(save_context->prepared_tasks_mutex));
3431 			if (++save_context->prepared_tasks >= save_context->total_tasks)
3432 				attachment_save_got_output_stream (save_context);
3433 			g_mutex_unlock (&(save_context->prepared_tasks_mutex));
3434 		}
3435 #endif
3436 		return;
3437 	}
3438 
3439 replace:
3440 	if (attachment->priv->save_self) {
3441 		g_file_replace_async (
3442 			destination, NULL, FALSE,
3443 			G_FILE_CREATE_REPLACE_DESTINATION,
3444 			G_PRIORITY_DEFAULT, cancellable,
3445 			(GAsyncReadyCallback) attachment_save_replace_cb,
3446 			save_context);
3447 	}
3448 
3449 #ifdef HAVE_AUTOAR
3450 	if (attachment->priv->save_extracted) {
3451 		/* We can safely use save_context->directory here because
3452 		 * attachment_save_replace_cb never calls
3453 		 * attachment_save_new_candidate, the only function using
3454 		 * the value of save_context->directory. */
3455 
3456 		save_context->suggested_destname =
3457 			g_file_get_basename (destination);
3458 		save_context->directory = g_file_get_parent (destination);
3459 		if (save_context->directory == NULL)
3460 			save_context->directory = g_object_ref (destination);
3461 
3462 		g_mutex_lock (&(save_context->prepared_tasks_mutex));
3463 		if (++save_context->prepared_tasks >= save_context->total_tasks)
3464 			attachment_save_got_output_stream (save_context);
3465 		g_mutex_unlock (&(save_context->prepared_tasks_mutex));
3466 	}
3467 #endif
3468 }
3469 
3470 void
e_attachment_save_async(EAttachment * attachment,GFile * destination,GAsyncReadyCallback callback,gpointer user_data)3471 e_attachment_save_async (EAttachment *attachment,
3472                          GFile *destination,
3473                          GAsyncReadyCallback callback,
3474                          gpointer user_data)
3475 {
3476 	SaveContext *save_context;
3477 	GCancellable *cancellable;
3478 
3479 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
3480 	g_return_if_fail (G_IS_FILE (destination));
3481 
3482 	if (e_attachment_get_loading (attachment)) {
3483 		g_simple_async_report_error_in_idle (
3484 			G_OBJECT (attachment), callback, user_data,
3485 			G_IO_ERROR, G_IO_ERROR_BUSY,
3486 			_("A load operation is already in progress"));
3487 		return;
3488 	}
3489 
3490 	if (e_attachment_get_saving (attachment)) {
3491 		g_simple_async_report_error_in_idle (
3492 			G_OBJECT (attachment), callback, user_data,
3493 			G_IO_ERROR, G_IO_ERROR_BUSY,
3494 			_("A save operation is already in progress"));
3495 		return;
3496 	}
3497 
3498 	/* Just peek, don't reference. */
3499 	if (attachment->priv->mime_part == NULL) {
3500 		g_simple_async_report_error_in_idle (
3501 			G_OBJECT (attachment), callback, user_data,
3502 			G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
3503 			_("Attachment contents not loaded"));
3504 		return;
3505 	}
3506 
3507 	save_context = attachment_save_context_new (
3508 		attachment, callback, user_data);
3509 
3510 	/* No task is not allowed. */
3511 	if (!attachment->priv->save_self && !attachment->priv->save_extracted)
3512 		attachment->priv->save_self = TRUE;
3513 
3514 	if (attachment->priv->save_self)
3515 		save_context->total_tasks++;
3516 #ifdef HAVE_AUTOAR
3517 	if (attachment->priv->save_extracted)
3518 		save_context->total_tasks++;
3519 #endif
3520 
3521 	cancellable = attachment->priv->cancellable;
3522 	g_cancellable_reset (cancellable);
3523 
3524 	/* First we need to know if destination is a directory. */
3525 	g_file_query_info_async (
3526 		destination, G_FILE_ATTRIBUTE_STANDARD_TYPE,
3527 		G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
3528 		cancellable, (GAsyncReadyCallback)
3529 		attachment_save_query_info_cb, save_context);
3530 }
3531 
3532 GFile *
e_attachment_save_finish(EAttachment * attachment,GAsyncResult * result,GError ** error)3533 e_attachment_save_finish (EAttachment *attachment,
3534                           GAsyncResult *result,
3535                           GError **error)
3536 {
3537 	GSimpleAsyncResult *simple;
3538 	GFile *destination;
3539 
3540 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
3541 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
3542 
3543 	simple = G_SIMPLE_ASYNC_RESULT (result);
3544 	if (g_simple_async_result_propagate_error (simple, error)) {
3545 		attachment_set_saving (attachment, FALSE);
3546 		return NULL;
3547 	}
3548 
3549 	destination = g_simple_async_result_get_op_res_gpointer (simple);
3550 	if (destination != NULL)
3551 		g_object_ref (destination);
3552 
3553 	attachment_set_saving (attachment, FALSE);
3554 
3555 	return destination;
3556 }
3557 
3558 void
e_attachment_save_handle_error(EAttachment * attachment,GAsyncResult * result,GtkWindow * parent)3559 e_attachment_save_handle_error (EAttachment *attachment,
3560                                 GAsyncResult *result,
3561                                 GtkWindow *parent)
3562 {
3563 	GFile *file;
3564 	GFileInfo *file_info;
3565 	GtkWidget *dialog;
3566 	const gchar *display_name;
3567 	const gchar *primary_text;
3568 	GError *error = NULL;
3569 
3570 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
3571 	g_return_if_fail (G_IS_ASYNC_RESULT (result));
3572 	g_return_if_fail (GTK_IS_WINDOW (parent));
3573 
3574 	file = e_attachment_save_finish (attachment, result, &error);
3575 
3576 	if (file != NULL) {
3577 		g_object_unref (file);
3578 		return;
3579 	}
3580 
3581 	/* Ignore cancellations. */
3582 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
3583 		return;
3584 
3585 	file_info = e_attachment_ref_file_info (attachment);
3586 
3587 	if (file_info != NULL)
3588 		display_name = g_file_info_get_display_name (file_info);
3589 	else
3590 		display_name = NULL;
3591 
3592 	if (display_name != NULL)
3593 		primary_text = g_strdup_printf (
3594 			_("Could not save “%s”"), display_name);
3595 	else
3596 		primary_text = g_strdup_printf (
3597 			_("Could not save the attachment"));
3598 
3599 	g_clear_object (&file_info);
3600 
3601 	dialog = gtk_message_dialog_new_with_markup (
3602 		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
3603 		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
3604 		"<big><b>%s</b></big>", primary_text);
3605 
3606 	gtk_message_dialog_format_secondary_text (
3607 		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
3608 
3609 	gtk_dialog_run (GTK_DIALOG (dialog));
3610 
3611 	gtk_widget_destroy (dialog);
3612 	g_error_free (error);
3613 }
3614 
3615 gboolean
e_attachment_save(EAttachment * attachment,GFile * in_destination,GFile ** out_destination,GError ** error)3616 e_attachment_save (EAttachment *attachment,
3617                    GFile *in_destination,
3618                    GFile **out_destination,
3619                    GError **error)
3620 {
3621 	EAsyncClosure *closure;
3622 	GAsyncResult *result;
3623 
3624 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
3625 	g_return_val_if_fail (out_destination != NULL, FALSE);
3626 
3627 	closure = e_async_closure_new ();
3628 
3629 	e_attachment_save_async (
3630 		attachment, in_destination,
3631 		e_async_closure_callback, closure);
3632 
3633 	result = e_async_closure_wait (closure);
3634 
3635 	*out_destination =
3636 		e_attachment_save_finish (attachment, result, error);
3637 
3638 	e_async_closure_free (closure);
3639 
3640 	return *out_destination != NULL;
3641 }
3642