1 /*
2  * e-attachment-store.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-store.h"
24 #include "e-icon-factory.h"
25 
26 #include <errno.h>
27 #include <glib/gi18n.h>
28 
29 #ifdef HAVE_AUTOAR
30 #include <gnome-autoar/gnome-autoar.h>
31 #include <gnome-autoar/autoar-gtk.h>
32 #endif
33 
34 #include "e-mktemp.h"
35 #include "e-misc-utils.h"
36 
37 #define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
38 	(G_TYPE_INSTANCE_GET_PRIVATE \
39 	((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))
40 
41 struct _EAttachmentStorePrivate {
42 	GHashTable *attachment_index;
43 
44 	guint ignore_row_changed : 1;
45 };
46 
47 enum {
48 	PROP_0,
49 	PROP_NUM_ATTACHMENTS,
50 	PROP_NUM_LOADING,
51 	PROP_TOTAL_SIZE
52 };
53 
54 enum {
55 	ATTACHMENT_ADDED,
56 	ATTACHMENT_REMOVED,
57 	LAST_SIGNAL
58 };
59 
60 static gulong signals[LAST_SIGNAL];
61 
G_DEFINE_TYPE(EAttachmentStore,e_attachment_store,GTK_TYPE_LIST_STORE)62 G_DEFINE_TYPE (
63 	EAttachmentStore,
64 	e_attachment_store,
65 	GTK_TYPE_LIST_STORE)
66 
67 static void
68 attachment_store_update_file_info_cb (EAttachment *attachment,
69 				      const gchar *caption,
70 				      const gchar *content_type,
71 				      const gchar *description,
72 				      gint64 size,
73 				      gpointer user_data)
74 {
75 	EAttachmentStore *store = user_data;
76 	GtkTreeIter iter;
77 
78 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
79 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
80 
81 	if (e_attachment_store_find_attachment_iter (store, attachment, &iter)) {
82 		gtk_list_store_set (
83 			GTK_LIST_STORE (store), &iter,
84 			E_ATTACHMENT_STORE_COLUMN_CAPTION, caption,
85 			E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, content_type,
86 			E_ATTACHMENT_STORE_COLUMN_DESCRIPTION, description,
87 			E_ATTACHMENT_STORE_COLUMN_SIZE, size,
88 			-1);
89 	}
90 }
91 
92 static void
attachment_store_update_icon_cb(EAttachment * attachment,GIcon * icon,gpointer user_data)93 attachment_store_update_icon_cb (EAttachment *attachment,
94 				 GIcon *icon,
95 				 gpointer user_data)
96 {
97 	EAttachmentStore *store = user_data;
98 	GtkTreeIter iter;
99 
100 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
101 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
102 
103 	if (e_attachment_store_find_attachment_iter (store, attachment, &iter)) {
104 		gtk_list_store_set (
105 			GTK_LIST_STORE (store), &iter,
106 			E_ATTACHMENT_STORE_COLUMN_ICON, icon,
107 			-1);
108 	}
109 }
110 
111 static void
attachment_store_update_progress_cb(EAttachment * attachment,gboolean loading,gboolean saving,gint percent,gpointer user_data)112 attachment_store_update_progress_cb (EAttachment *attachment,
113 				     gboolean loading,
114 				     gboolean saving,
115 				     gint percent,
116 				     gpointer user_data)
117 {
118 	EAttachmentStore *store = user_data;
119 	GtkTreeIter iter;
120 
121 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
122 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
123 
124 	if (e_attachment_store_find_attachment_iter (store, attachment, &iter)) {
125 		gtk_list_store_set (
126 			GTK_LIST_STORE (store), &iter,
127 			E_ATTACHMENT_STORE_COLUMN_LOADING, loading,
128 			E_ATTACHMENT_STORE_COLUMN_SAVING, saving,
129 			E_ATTACHMENT_STORE_COLUMN_PERCENT, percent,
130 			-1);
131 	}
132 }
133 
134 static void
attachment_store_load_failed_cb(EAttachment * attachment,gpointer user_data)135 attachment_store_load_failed_cb (EAttachment *attachment,
136 				 gpointer user_data)
137 {
138 	EAttachmentStore *store = user_data;
139 
140 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
141 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
142 
143 	e_attachment_store_remove_attachment (store, attachment);
144 }
145 
146 static void
attachment_store_attachment_notify_cb(GObject * attachment,GParamSpec * param,gpointer user_data)147 attachment_store_attachment_notify_cb (GObject *attachment,
148 				       GParamSpec *param,
149 				       gpointer user_data)
150 {
151 	EAttachmentStore *store = user_data;
152 
153 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
154 	g_return_if_fail (param != NULL);
155 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
156 
157 	if (g_str_equal (param->name, "loading")) {
158 		g_object_notify (G_OBJECT (store), "num-loading");
159 	} else if (g_str_equal (param->name, "file-info")) {
160 		g_object_notify (G_OBJECT (store), "total-size");
161 	}
162 }
163 
164 static void
attachment_store_attachment_added(EAttachmentStore * store,EAttachment * attachment)165 attachment_store_attachment_added (EAttachmentStore *store,
166 				   EAttachment *attachment)
167 {
168 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
169 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
170 
171 	g_signal_connect (attachment, "update-file-info",
172 		G_CALLBACK (attachment_store_update_file_info_cb), store);
173 	g_signal_connect (attachment, "update-icon",
174 		G_CALLBACK (attachment_store_update_icon_cb), store);
175 	g_signal_connect (attachment, "update-progress",
176 		G_CALLBACK (attachment_store_update_progress_cb), store);
177 	g_signal_connect (attachment, "load-failed",
178 		G_CALLBACK (attachment_store_load_failed_cb), store);
179 	g_signal_connect (attachment, "notify",
180 		G_CALLBACK (attachment_store_attachment_notify_cb), store);
181 
182 	e_attachment_update_store_columns (attachment);
183 }
184 
185 static void
attachment_store_attachment_removed(EAttachmentStore * store,EAttachment * attachment)186 attachment_store_attachment_removed (EAttachmentStore *store,
187 				     EAttachment *attachment)
188 {
189 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
190 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
191 
192 	g_signal_handlers_disconnect_by_func (attachment,
193 		G_CALLBACK (attachment_store_update_file_info_cb), store);
194 	g_signal_handlers_disconnect_by_func (attachment,
195 		G_CALLBACK (attachment_store_update_icon_cb), store);
196 	g_signal_handlers_disconnect_by_func (attachment,
197 		G_CALLBACK (attachment_store_update_progress_cb), store);
198 	g_signal_handlers_disconnect_by_func (attachment,
199 		G_CALLBACK (attachment_store_load_failed_cb), store);
200 	g_signal_handlers_disconnect_by_func (attachment,
201 		G_CALLBACK (attachment_store_attachment_notify_cb), store);
202 }
203 
204 static void
attachment_store_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)205 attachment_store_get_property (GObject *object,
206                                guint property_id,
207                                GValue *value,
208                                GParamSpec *pspec)
209 {
210 	switch (property_id) {
211 		case PROP_NUM_ATTACHMENTS:
212 			g_value_set_uint (
213 				value,
214 				e_attachment_store_get_num_attachments (
215 				E_ATTACHMENT_STORE (object)));
216 			return;
217 
218 		case PROP_NUM_LOADING:
219 			g_value_set_uint (
220 				value,
221 				e_attachment_store_get_num_loading (
222 				E_ATTACHMENT_STORE (object)));
223 			return;
224 
225 		case PROP_TOTAL_SIZE:
226 			g_value_set_uint64 (
227 				value,
228 				e_attachment_store_get_total_size (
229 				E_ATTACHMENT_STORE (object)));
230 			return;
231 	}
232 
233 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
234 }
235 
236 static void
attachment_store_dispose(GObject * object)237 attachment_store_dispose (GObject *object)
238 {
239 	e_attachment_store_remove_all (E_ATTACHMENT_STORE (object));
240 
241 	/* Chain up to parent's dispose() method. */
242 	G_OBJECT_CLASS (e_attachment_store_parent_class)->dispose (object);
243 }
244 
245 static void
attachment_store_finalize(GObject * object)246 attachment_store_finalize (GObject *object)
247 {
248 	EAttachmentStorePrivate *priv;
249 
250 	priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
251 
252 	g_hash_table_destroy (priv->attachment_index);
253 
254 	/* Chain up to parent's finalize() method. */
255 	G_OBJECT_CLASS (e_attachment_store_parent_class)->finalize (object);
256 }
257 
258 static void
e_attachment_store_class_init(EAttachmentStoreClass * class)259 e_attachment_store_class_init (EAttachmentStoreClass *class)
260 {
261 	GObjectClass *object_class;
262 
263 	g_type_class_add_private (class, sizeof (EAttachmentStorePrivate));
264 
265 	object_class = G_OBJECT_CLASS (class);
266 	object_class->get_property = attachment_store_get_property;
267 	object_class->dispose = attachment_store_dispose;
268 	object_class->finalize = attachment_store_finalize;
269 
270 	class->attachment_added = attachment_store_attachment_added;
271 	class->attachment_removed = attachment_store_attachment_removed;
272 
273 	g_object_class_install_property (
274 		object_class,
275 		PROP_NUM_ATTACHMENTS,
276 		g_param_spec_uint (
277 			"num-attachments",
278 			"Num Attachments",
279 			NULL,
280 			0,
281 			G_MAXUINT,
282 			0,
283 			G_PARAM_READABLE));
284 
285 	g_object_class_install_property (
286 		object_class,
287 		PROP_NUM_LOADING,
288 		g_param_spec_uint (
289 			"num-loading",
290 			"Num Loading",
291 			NULL,
292 			0,
293 			G_MAXUINT,
294 			0,
295 			G_PARAM_READABLE));
296 
297 	g_object_class_install_property (
298 		object_class,
299 		PROP_TOTAL_SIZE,
300 		g_param_spec_uint64 (
301 			"total-size",
302 			"Total Size",
303 			NULL,
304 			0,
305 			G_MAXUINT64,
306 			0,
307 			G_PARAM_READABLE));
308 
309 	signals[ATTACHMENT_ADDED] = g_signal_new (
310 		"attachment-added",
311 		G_TYPE_FROM_CLASS (class),
312 		G_SIGNAL_RUN_LAST,
313 		G_STRUCT_OFFSET (EAttachmentStoreClass, attachment_added),
314 		NULL, NULL, NULL,
315 		G_TYPE_NONE, 1, E_TYPE_ATTACHMENT);
316 
317 	signals[ATTACHMENT_REMOVED] = g_signal_new (
318 		"attachment-removed",
319 		G_TYPE_FROM_CLASS (class),
320 		G_SIGNAL_RUN_LAST,
321 		G_STRUCT_OFFSET (EAttachmentStoreClass, attachment_removed),
322 		NULL, NULL, NULL,
323 		G_TYPE_NONE, 1, E_TYPE_ATTACHMENT);
324 }
325 
326 static void
e_attachment_store_init(EAttachmentStore * store)327 e_attachment_store_init (EAttachmentStore *store)
328 {
329 	GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
330 	GHashTable *attachment_index;
331 	gint column = 0;
332 
333 	attachment_index = g_hash_table_new_full (
334 		g_direct_hash, g_direct_equal,
335 		(GDestroyNotify) g_object_unref,
336 		(GDestroyNotify) gtk_tree_row_reference_free);
337 
338 	store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
339 	store->priv->attachment_index = attachment_index;
340 
341 	types[column++] = E_TYPE_ATTACHMENT;	/* COLUMN_ATTACHMENT */
342 	types[column++] = G_TYPE_STRING;	/* COLUMN_CAPTION */
343 	types[column++] = G_TYPE_STRING;	/* COLUMN_CONTENT_TYPE */
344 	types[column++] = G_TYPE_STRING;	/* COLUMN_DESCRIPTION */
345 	types[column++] = G_TYPE_ICON;		/* COLUMN_ICON */
346 	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_LOADING */
347 	types[column++] = G_TYPE_INT;		/* COLUMN_PERCENT */
348 	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_SAVING */
349 	types[column++] = G_TYPE_UINT64;	/* COLUMN_SIZE */
350 
351 	g_return_if_fail (column == E_ATTACHMENT_STORE_NUM_COLUMNS);
352 
353 	gtk_list_store_set_column_types (
354 		GTK_LIST_STORE (store), G_N_ELEMENTS (types), types);
355 }
356 
357 GtkTreeModel *
e_attachment_store_new(void)358 e_attachment_store_new (void)
359 {
360 	return g_object_new (E_TYPE_ATTACHMENT_STORE, NULL);
361 }
362 
363 void
e_attachment_store_add_attachment(EAttachmentStore * store,EAttachment * attachment)364 e_attachment_store_add_attachment (EAttachmentStore *store,
365                                    EAttachment *attachment)
366 {
367 	GtkTreeRowReference *reference;
368 	GtkTreeModel *model;
369 	GtkTreePath *path;
370 	GtkTreeIter iter;
371 
372 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
373 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
374 
375 	gtk_list_store_append (GTK_LIST_STORE (store), &iter);
376 
377 	gtk_list_store_set (
378 		GTK_LIST_STORE (store), &iter,
379 		E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, attachment, -1);
380 
381 	model = GTK_TREE_MODEL (store);
382 	path = gtk_tree_model_get_path (model, &iter);
383 	reference = gtk_tree_row_reference_new (model, path);
384 	gtk_tree_path_free (path);
385 
386 	g_hash_table_insert (
387 		store->priv->attachment_index,
388 		g_object_ref (attachment), reference);
389 
390 	g_object_freeze_notify (G_OBJECT (store));
391 	g_object_notify (G_OBJECT (store), "num-attachments");
392 	g_object_notify (G_OBJECT (store), "total-size");
393 	g_object_thaw_notify (G_OBJECT (store));
394 
395 	g_signal_emit (store, signals[ATTACHMENT_ADDED], 0, attachment);
396 }
397 
398 gboolean
e_attachment_store_remove_attachment(EAttachmentStore * store,EAttachment * attachment)399 e_attachment_store_remove_attachment (EAttachmentStore *store,
400                                       EAttachment *attachment)
401 {
402 	GtkTreeRowReference *reference;
403 	GHashTable *hash_table;
404 	GtkTreeModel *model;
405 	GtkTreePath *path;
406 	GtkTreeIter iter;
407 	gboolean removed;
408 
409 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
410 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
411 
412 	hash_table = store->priv->attachment_index;
413 	reference = g_hash_table_lookup (hash_table, attachment);
414 
415 	if (reference == NULL)
416 		return FALSE;
417 
418 	if (!gtk_tree_row_reference_valid (reference)) {
419 		if (g_hash_table_remove (hash_table, attachment))
420 			g_signal_emit (store, signals[ATTACHMENT_REMOVED], 0, attachment);
421 		return FALSE;
422 	}
423 
424 	e_attachment_cancel (attachment);
425 
426 	model = gtk_tree_row_reference_get_model (reference);
427 	path = gtk_tree_row_reference_get_path (reference);
428 	gtk_tree_model_get_iter (model, &iter, path);
429 	gtk_tree_path_free (path);
430 
431 	gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
432 	removed = g_hash_table_remove (hash_table, attachment);
433 
434 	g_object_freeze_notify (G_OBJECT (store));
435 	g_object_notify (G_OBJECT (store), "num-attachments");
436 	g_object_notify (G_OBJECT (store), "total-size");
437 	g_object_thaw_notify (G_OBJECT (store));
438 
439 	if (removed)
440 		g_signal_emit (store, signals[ATTACHMENT_REMOVED], 0, attachment);
441 
442 	return TRUE;
443 }
444 
445 void
e_attachment_store_remove_all(EAttachmentStore * store)446 e_attachment_store_remove_all (EAttachmentStore *store)
447 {
448 	GList *list, *iter;
449 
450 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
451 
452 	if (!g_hash_table_size (store->priv->attachment_index))
453 		return;
454 
455 	g_object_freeze_notify (G_OBJECT (store));
456 
457 	/* Get the list of attachments before clearing the list store,
458 	   otherwise there would be returned no attachments. */
459 	list = e_attachment_store_get_attachments (store);
460 
461 	/* Clear the list store before cancelling EAttachment load/save
462 	 * operations.  This will invalidate the EAttachment's tree row
463 	 * reference so it won't try to update the row's icon column in
464 	 * response to the cancellation.  That can create problems when
465 	 * the list store is being disposed. */
466 	gtk_list_store_clear (GTK_LIST_STORE (store));
467 
468 	for (iter = list; iter; iter = iter->next) {
469 		EAttachment *attachment = iter->data;
470 
471 		e_attachment_cancel (attachment);
472 
473 		g_warn_if_fail (g_hash_table_remove (store->priv->attachment_index, attachment));
474 
475 		g_signal_emit (store, signals[ATTACHMENT_REMOVED], 0, attachment);
476 	}
477 
478 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
479 	g_list_free (list);
480 
481 	g_object_notify (G_OBJECT (store), "num-attachments");
482 	g_object_notify (G_OBJECT (store), "total-size");
483 	g_object_thaw_notify (G_OBJECT (store));
484 }
485 
486 void
e_attachment_store_add_to_multipart(EAttachmentStore * store,CamelMultipart * multipart,const gchar * default_charset)487 e_attachment_store_add_to_multipart (EAttachmentStore *store,
488                                      CamelMultipart *multipart,
489                                      const gchar *default_charset)
490 {
491 	GList *list, *iter;
492 
493 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
494 	g_return_if_fail (CAMEL_MULTIPART (multipart));
495 
496 	list = e_attachment_store_get_attachments (store);
497 
498 	for (iter = list; iter != NULL; iter = iter->next) {
499 		EAttachment *attachment = iter->data;
500 
501 		/* Skip the attachment if it's still loading. */
502 		if (!e_attachment_get_loading (attachment))
503 			e_attachment_add_to_multipart (
504 				attachment, multipart, default_charset);
505 	}
506 
507 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
508 	g_list_free (list);
509 }
510 
511 GList *
e_attachment_store_get_attachments(EAttachmentStore * store)512 e_attachment_store_get_attachments (EAttachmentStore *store)
513 {
514 	GList *list = NULL;
515 	GtkTreeModel *model;
516 	GtkTreeIter iter;
517 	gboolean valid;
518 
519 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
520 
521 	model = GTK_TREE_MODEL (store);
522 	valid = gtk_tree_model_get_iter_first (model, &iter);
523 
524 	while (valid) {
525 		EAttachment *attachment;
526 		gint column_id;
527 
528 		column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
529 		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
530 
531 		list = g_list_prepend (list, attachment);
532 
533 		valid = gtk_tree_model_iter_next (model, &iter);
534 	}
535 
536 	return g_list_reverse (list);
537 }
538 
539 guint
e_attachment_store_get_num_attachments(EAttachmentStore * store)540 e_attachment_store_get_num_attachments (EAttachmentStore *store)
541 {
542 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
543 
544 	return g_hash_table_size (store->priv->attachment_index);
545 }
546 
547 guint
e_attachment_store_get_num_loading(EAttachmentStore * store)548 e_attachment_store_get_num_loading (EAttachmentStore *store)
549 {
550 	GList *list, *iter;
551 	guint num_loading = 0;
552 
553 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
554 
555 	list = e_attachment_store_get_attachments (store);
556 
557 	for (iter = list; iter != NULL; iter = iter->next) {
558 		EAttachment *attachment = iter->data;
559 
560 		if (e_attachment_get_loading (attachment))
561 			num_loading++;
562 	}
563 
564 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
565 	g_list_free (list);
566 
567 	return num_loading;
568 }
569 
570 goffset
e_attachment_store_get_total_size(EAttachmentStore * store)571 e_attachment_store_get_total_size (EAttachmentStore *store)
572 {
573 	GList *list, *iter;
574 	goffset total_size = 0;
575 
576 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
577 
578 	list = e_attachment_store_get_attachments (store);
579 
580 	for (iter = list; iter != NULL; iter = iter->next) {
581 		EAttachment *attachment = iter->data;
582 		GFileInfo *file_info;
583 
584 		file_info = e_attachment_ref_file_info (attachment);
585 		if (file_info != NULL) {
586 			total_size += g_file_info_get_size (file_info);
587 			g_object_unref (file_info);
588 		}
589 	}
590 
591 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
592 	g_list_free (list);
593 
594 	return total_size;
595 }
596 
597 static void
update_preview_cb(GtkFileChooser * file_chooser,gpointer data)598 update_preview_cb (GtkFileChooser *file_chooser,
599                    gpointer data)
600 {
601 	GtkWidget *preview;
602 	gchar *filename = NULL;
603 	GdkPixbuf *pixbuf;
604 
605 	gtk_file_chooser_set_preview_widget_active (file_chooser, FALSE);
606 	gtk_image_clear (GTK_IMAGE (data));
607 	preview = GTK_WIDGET (data);
608 	filename = gtk_file_chooser_get_preview_filename (file_chooser);
609 	if (!e_util_can_preview_filename (filename)) {
610 		g_free (filename);
611 		return;
612 	}
613 
614 	pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 128, 128, NULL);
615 	g_free (filename);
616 	if (!pixbuf)
617 		return;
618 
619 	gtk_file_chooser_set_preview_widget_active (file_chooser, TRUE);
620 	gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf);
621 	g_object_unref (pixbuf);
622 }
623 
624 void
e_attachment_store_run_load_dialog(EAttachmentStore * store,GtkWindow * parent)625 e_attachment_store_run_load_dialog (EAttachmentStore *store,
626                                     GtkWindow *parent)
627 {
628 	GtkFileChooser *file_chooser;
629 	GtkWidget *dialog = NULL;
630 	GtkFileChooserNative *native = NULL;
631 	GtkBox *extra_box;
632 	GtkWidget *extra_box_widget;
633 	GtkWidget *option_display = NULL;
634 	GtkImage *preview;
635 	GSList *files, *iter;
636 	const gchar *disposition;
637 	gboolean active;
638 	gint response;
639 #ifdef HAVE_AUTOAR
640 	GtkBox *option_format_box;
641 	GtkWidget *option_format_box_widget;
642 	GtkWidget *option_format_label;
643 	GtkWidget *option_format_combo;
644 	GSettings *settings = NULL;
645 	gchar *format_string = NULL;
646 	gchar *filter_string = NULL;
647 	gint format;
648 	gint filter;
649 #endif
650 
651 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
652 	g_return_if_fail (GTK_IS_WINDOW (parent));
653 
654 	if (e_util_is_running_flatpak ()) {
655 		native = gtk_file_chooser_native_new (
656 			_("Add Attachment"), parent,
657 			GTK_FILE_CHOOSER_ACTION_OPEN,
658 			_("A_ttach"), _("_Cancel"));
659 
660 		file_chooser = GTK_FILE_CHOOSER (native);
661 	} else {
662 		dialog = gtk_file_chooser_dialog_new (
663 			_("Add Attachment"), parent,
664 			GTK_FILE_CHOOSER_ACTION_OPEN,
665 #ifdef HAVE_AUTOAR
666 			_("_Open"), GTK_RESPONSE_ACCEPT,
667 #endif
668 			_("_Cancel"), GTK_RESPONSE_CANCEL,
669 #ifdef HAVE_AUTOAR
670 			_("A_ttach"), GTK_RESPONSE_CLOSE,
671 #else
672 			_("A_ttach"), GTK_RESPONSE_ACCEPT,
673 #endif
674 			NULL);
675 
676 		file_chooser = GTK_FILE_CHOOSER (dialog);
677 	}
678 
679 	gtk_file_chooser_set_local_only (file_chooser, FALSE);
680 	gtk_file_chooser_set_select_multiple (file_chooser, TRUE);
681 
682 	if (dialog) {
683 #ifdef HAVE_AUTOAR
684 		gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
685 #else
686 		gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
687 #endif
688 		gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
689 
690 		preview = GTK_IMAGE (gtk_image_new ());
691 		gtk_file_chooser_set_preview_widget (file_chooser, GTK_WIDGET (preview));
692 		g_signal_connect (
693 			file_chooser, "update-preview",
694 			G_CALLBACK (update_preview_cb), preview);
695 
696 		extra_box_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
697 		extra_box = GTK_BOX (extra_box_widget);
698 
699 		option_display = gtk_check_button_new_with_mnemonic (
700 			_("_Suggest automatic display of attachment"));
701 		gtk_box_pack_start (extra_box, option_display, FALSE, FALSE, 0);
702 
703 #ifdef HAVE_AUTOAR
704 		option_format_box_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
705 		option_format_box = GTK_BOX (option_format_box_widget);
706 		gtk_box_pack_start (extra_box, option_format_box_widget, FALSE, FALSE, 0);
707 
708 		settings = e_util_ref_settings ("org.gnome.evolution.shell");
709 
710 		format_string = g_settings_get_string (settings, "autoar-format");
711 		filter_string = g_settings_get_string (settings, "autoar-filter");
712 
713 		if (!e_enum_from_string (AUTOAR_TYPE_FORMAT, format_string, &format)) {
714 			format = AUTOAR_FORMAT_ZIP;
715 		}
716 		if (!e_enum_from_string (AUTOAR_TYPE_FILTER, filter_string, &filter)) {
717 			filter = AUTOAR_FILTER_NONE;
718 		}
719 
720 		option_format_label = gtk_label_new (
721 			_("Archive selected directories using this format:"));
722 		option_format_combo = autoar_gtk_chooser_simple_new (
723 			format,
724 			filter);
725 		gtk_box_pack_start (option_format_box, option_format_label, FALSE, FALSE, 0);
726 		gtk_box_pack_start (option_format_box, option_format_combo, FALSE, FALSE, 0);
727 #endif
728 
729 		gtk_file_chooser_set_extra_widget (file_chooser, extra_box_widget);
730 		gtk_widget_show_all (extra_box_widget);
731 	}
732 
733 	e_util_load_file_chooser_folder (file_chooser);
734 
735 	if (dialog)
736 		response = gtk_dialog_run (GTK_DIALOG (dialog));
737 	else
738 		response = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
739 
740 #ifdef HAVE_AUTOAR
741 	if (response != GTK_RESPONSE_ACCEPT && response != GTK_RESPONSE_CLOSE)
742 #else
743 	if (response != GTK_RESPONSE_ACCEPT)
744 #endif
745 		goto exit;
746 
747 	e_util_save_file_chooser_folder (file_chooser);
748 
749 	files = gtk_file_chooser_get_files (file_chooser);
750 	active = option_display ? gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (option_display)) : FALSE;
751 	disposition = active ? "inline" : "attachment";
752 
753 #ifdef HAVE_AUTOAR
754 	if (dialog) {
755 		autoar_gtk_chooser_simple_get (option_format_combo, &format, &filter);
756 
757 		if (!e_enum_to_string (AUTOAR_TYPE_FORMAT, format)) {
758 			format = AUTOAR_FORMAT_ZIP;
759 		}
760 
761 		if (!e_enum_to_string (AUTOAR_TYPE_FORMAT, filter)) {
762 			filter = AUTOAR_FILTER_NONE;
763 		}
764 
765 		g_settings_set_string (
766 			settings,
767 			"autoar-format",
768 			e_enum_to_string (AUTOAR_TYPE_FORMAT, format));
769 		g_settings_set_string (
770 			settings,
771 			"autoar-filter",
772 			e_enum_to_string (AUTOAR_TYPE_FILTER, filter));
773 	}
774 #endif
775 
776 	for (iter = files; iter != NULL; iter = g_slist_next (iter)) {
777 		EAttachment *attachment;
778 		GFile *file = iter->data;
779 
780 		attachment = e_attachment_new ();
781 		e_attachment_set_file (attachment, file);
782 		e_attachment_set_disposition (attachment, disposition);
783 		e_attachment_store_add_attachment (store, attachment);
784 
785 		e_attachment_load_async (
786 			attachment, (GAsyncReadyCallback)
787 			e_attachment_load_handle_error, parent);
788 		g_object_unref (attachment);
789 	}
790 
791 	g_slist_foreach (files, (GFunc) g_object_unref, NULL);
792 	g_slist_free (files);
793 
794  exit:
795 	if (dialog)
796 		gtk_widget_destroy (dialog);
797 	else
798 		g_clear_object (&native);
799 
800 #ifdef HAVE_AUTOAR
801 	g_clear_object (&settings);
802 	g_free (format_string);
803 	g_free (filter_string);
804 #endif
805 }
806 
807 GFile *
e_attachment_store_run_save_dialog(EAttachmentStore * store,GList * attachment_list,GtkWindow * parent)808 e_attachment_store_run_save_dialog (EAttachmentStore *store,
809                                     GList *attachment_list,
810                                     GtkWindow *parent)
811 {
812 	GtkFileChooser *file_chooser;
813 	GtkFileChooserAction action;
814 	GtkWidget *dialog = NULL;
815 	GtkFileChooserNative *native = NULL;
816 	GFile *destination;
817 	const gchar *title;
818 	gint response;
819 	guint length;
820 #ifdef HAVE_AUTOAR
821 	GtkBox *extra_box;
822 	GtkWidget *extra_box_widget = NULL;
823 
824 	GtkBox *extract_box;
825 	GtkWidget *extract_box_widget;
826 
827 	GSList *extract_group;
828 	GtkWidget *extract_dont, *extract_only, *extract_org;
829 #endif
830 
831 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
832 
833 	length = g_list_length (attachment_list);
834 
835 	if (length == 0)
836 		return NULL;
837 
838 	title = ngettext ("Save Attachment", "Save Attachments", length);
839 
840 	if (length == 1)
841 		action = GTK_FILE_CHOOSER_ACTION_SAVE;
842 	else
843 		action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
844 
845 	if (e_util_is_running_flatpak ()) {
846 		native = gtk_file_chooser_native_new (
847 			title, GTK_WINDOW (parent), action,
848 			_("_Save"), _("_Cancel"));
849 
850 		file_chooser = GTK_FILE_CHOOSER (native);
851 	} else {
852 		dialog = gtk_file_chooser_dialog_new (
853 			title, parent, action,
854 			_("_Cancel"), GTK_RESPONSE_CANCEL,
855 			_("_Save"), GTK_RESPONSE_ACCEPT, NULL);
856 
857 		file_chooser = GTK_FILE_CHOOSER (dialog);
858 	}
859 
860 	gtk_file_chooser_set_local_only (file_chooser, FALSE);
861 	gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
862 
863 	if (dialog) {
864 		gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
865 		gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
866 
867 #ifdef HAVE_AUTOAR
868 		extra_box_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
869 		extra_box = GTK_BOX (extra_box_widget);
870 
871 		extract_box_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
872 		extract_box = GTK_BOX (extract_box_widget);
873 		gtk_box_pack_start (extra_box, extract_box_widget, FALSE, FALSE, 5);
874 
875 		extract_dont = gtk_radio_button_new_with_mnemonic (NULL,
876 			_("Do _not extract files from the attachment"));
877 		gtk_box_pack_start (extract_box, extract_dont, FALSE, FALSE, 0);
878 
879 		extract_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (extract_dont));
880 		extract_only = gtk_radio_button_new_with_mnemonic (extract_group,
881 			_("Save extracted files _only"));
882 		gtk_box_pack_start (extract_box, extract_only, FALSE, FALSE, 0);
883 
884 		extract_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (extract_only));
885 		extract_org = gtk_radio_button_new_with_mnemonic (extract_group,
886 			_("Save extracted files and the original _archive"));
887 		gtk_box_pack_start (extract_box, extract_org, FALSE, FALSE, 0);
888 
889 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (extract_dont), TRUE);
890 
891 		gtk_widget_show_all (extra_box_widget);
892 		gtk_file_chooser_set_extra_widget (file_chooser, extra_box_widget);
893 #endif
894 	}
895 
896 	if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
897 		EAttachment *attachment;
898 		GFileInfo *file_info;
899 		const gchar *name = NULL;
900 		gchar *allocated;
901 
902 #ifdef HAVE_AUTOAR
903 		gchar *mime_type;
904 #endif
905 
906 		attachment = attachment_list->data;
907 		file_info = e_attachment_ref_file_info (attachment);
908 
909 		if (file_info != NULL)
910 			name = g_file_info_get_display_name (file_info);
911 
912 		if (name == NULL)
913 			/* Translators: Default attachment filename. */
914 			name = _("attachment.dat");
915 
916 		allocated = g_strdup (name);
917 		e_util_make_safe_filename (allocated);
918 
919 		gtk_file_chooser_set_current_name (file_chooser, allocated);
920 
921 		g_free (allocated);
922 
923 #ifdef HAVE_AUTOAR
924 		mime_type = e_attachment_dup_mime_type (attachment);
925 		if (dialog && !autoar_check_mime_type_supported (mime_type)) {
926 			gtk_widget_hide (extra_box_widget);
927 		}
928 
929 		g_free (mime_type);
930 #endif
931 
932 		g_clear_object (&file_info);
933 #ifdef HAVE_AUTOAR
934 	} else if (dialog) {
935 		GList *iter;
936 		gboolean any_supported = FALSE;
937 
938 		for (iter = attachment_list; iter && !any_supported; iter = iter->next) {
939 			EAttachment *attachment = iter->data;
940 			gchar *mime_type;
941 
942 			mime_type = e_attachment_dup_mime_type (attachment);
943 
944 			any_supported = autoar_check_mime_type_supported (mime_type);
945 
946 			g_free (mime_type);
947 		}
948 
949 		gtk_widget_set_visible (extra_box_widget, any_supported);
950 #endif
951 	}
952 
953 	e_util_load_file_chooser_folder (file_chooser);
954 
955 	if (dialog)
956 		response = gtk_dialog_run (GTK_DIALOG (dialog));
957 	else
958 		response = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
959 
960 	if (response == GTK_RESPONSE_ACCEPT) {
961 #ifdef HAVE_AUTOAR
962 		gboolean save_self, save_extracted;
963 #endif
964 
965 		e_util_save_file_chooser_folder (file_chooser);
966 		destination = gtk_file_chooser_get_file (file_chooser);
967 
968 		if (dialog) {
969 #ifdef HAVE_AUTOAR
970 			save_self =
971 				gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (extract_dont)) ||
972 				gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (extract_org));
973 			save_extracted =
974 				gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (extract_only)) ||
975 				gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (extract_org));
976 
977 			if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
978 				e_attachment_set_save_self (attachment_list->data, save_self);
979 				e_attachment_set_save_extracted (attachment_list->data, save_extracted);
980 			} else {
981 				GList *iter;
982 
983 				for (iter = attachment_list; iter != NULL; iter = iter->next) {
984 					EAttachment *attachment;
985 					gchar *mime_type;
986 
987 					attachment = iter->data;
988 					mime_type = e_attachment_dup_mime_type (attachment);
989 
990 					if (autoar_check_mime_type_supported (mime_type)) {
991 						e_attachment_set_save_self (attachment, save_self);
992 						e_attachment_set_save_extracted (attachment, save_extracted);
993 					} else {
994 						e_attachment_set_save_self (attachment, TRUE);
995 						e_attachment_set_save_extracted (attachment, FALSE);
996 					}
997 
998 					g_free (mime_type);
999 				}
1000 			}
1001 #endif
1002 		}
1003 	} else {
1004 		destination = NULL;
1005 	}
1006 
1007 	if (dialog)
1008 		gtk_widget_destroy (dialog);
1009 	else
1010 		g_clear_object (&native);
1011 
1012 	return destination;
1013 }
1014 
1015 gboolean
e_attachment_store_transform_num_attachments_to_visible_boolean(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)1016 e_attachment_store_transform_num_attachments_to_visible_boolean (GBinding *binding,
1017 								 const GValue *from_value,
1018 								 GValue *to_value,
1019 								 gpointer user_data)
1020 {
1021 	g_return_val_if_fail (from_value != NULL, FALSE);
1022 	g_return_val_if_fail (to_value != NULL, FALSE);
1023 	g_return_val_if_fail (G_VALUE_HOLDS_UINT (from_value), FALSE);
1024 	g_return_val_if_fail (G_VALUE_HOLDS_BOOLEAN (to_value), FALSE);
1025 
1026 	g_value_set_boolean (to_value, g_value_get_uint (from_value) != 0);
1027 
1028 	return TRUE;
1029 }
1030 
1031 gboolean
e_attachment_store_find_attachment_iter(EAttachmentStore * store,EAttachment * attachment,GtkTreeIter * out_iter)1032 e_attachment_store_find_attachment_iter (EAttachmentStore *store,
1033 					 EAttachment *attachment,
1034 					 GtkTreeIter *out_iter)
1035 {
1036 	GtkTreeRowReference *reference;
1037 	GtkTreeModel *model;
1038 	GtkTreePath *path;
1039 	gboolean found;
1040 
1041 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
1042 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1043 	g_return_val_if_fail (out_iter != NULL, FALSE);
1044 
1045 	reference = g_hash_table_lookup (store->priv->attachment_index, attachment);
1046 
1047 	if (!reference || !gtk_tree_row_reference_valid (reference))
1048 		return FALSE;
1049 
1050 	model = gtk_tree_row_reference_get_model (reference);
1051 	g_return_val_if_fail (model == GTK_TREE_MODEL (store), FALSE);
1052 
1053 	path = gtk_tree_row_reference_get_path (reference);
1054 	found = gtk_tree_model_get_iter (model, out_iter, path);
1055 	gtk_tree_path_free (path);
1056 
1057 	return found;
1058 }
1059 
1060 /******************** e_attachment_store_get_uris_async() ********************/
1061 
1062 typedef struct _UriContext UriContext;
1063 
1064 struct _UriContext {
1065 	GSimpleAsyncResult *simple;
1066 	GList *attachment_list;
1067 	GError *error;
1068 	gchar **uris;
1069 	gint index;
1070 };
1071 
1072 static UriContext *
attachment_store_uri_context_new(EAttachmentStore * store,GList * attachment_list,GAsyncReadyCallback callback,gpointer user_data)1073 attachment_store_uri_context_new (EAttachmentStore *store,
1074                                   GList *attachment_list,
1075                                   GAsyncReadyCallback callback,
1076                                   gpointer user_data)
1077 {
1078 	UriContext *uri_context;
1079 	GSimpleAsyncResult *simple;
1080 	guint length;
1081 	gchar **uris;
1082 
1083 	simple = g_simple_async_result_new (
1084 		G_OBJECT (store), callback, user_data,
1085 		e_attachment_store_get_uris_async);
1086 
1087 	/* Add one for NULL terminator. */
1088 	length = g_list_length (attachment_list) + 1;
1089 	uris = g_malloc0 (sizeof (gchar *) * length);
1090 
1091 	uri_context = g_slice_new0 (UriContext);
1092 	uri_context->simple = simple;
1093 	uri_context->attachment_list = g_list_copy (attachment_list);
1094 	uri_context->uris = uris;
1095 
1096 	g_list_foreach (
1097 		uri_context->attachment_list,
1098 		(GFunc) g_object_ref, NULL);
1099 
1100 	return uri_context;
1101 }
1102 
1103 static void
attachment_store_uri_context_free(UriContext * uri_context)1104 attachment_store_uri_context_free (UriContext *uri_context)
1105 {
1106 	g_object_unref (uri_context->simple);
1107 
1108 	/* The attachment list should be empty now. */
1109 	g_warn_if_fail (uri_context->attachment_list == NULL);
1110 
1111 	/* So should the error. */
1112 	g_warn_if_fail (uri_context->error == NULL);
1113 
1114 	g_strfreev (uri_context->uris);
1115 
1116 	g_slice_free (UriContext, uri_context);
1117 }
1118 
1119 static void
attachment_store_get_uris_save_cb(EAttachment * attachment,GAsyncResult * result,UriContext * uri_context)1120 attachment_store_get_uris_save_cb (EAttachment *attachment,
1121                                    GAsyncResult *result,
1122                                    UriContext *uri_context)
1123 {
1124 	GSimpleAsyncResult *simple;
1125 	GFile *file;
1126 	gchar **uris;
1127 	gchar *uri;
1128 	GError *error = NULL;
1129 
1130 	file = e_attachment_save_finish (attachment, result, &error);
1131 
1132 	/* Remove the attachment from the list. */
1133 	uri_context->attachment_list = g_list_remove (
1134 		uri_context->attachment_list, attachment);
1135 	g_object_unref (attachment);
1136 
1137 	if (file != NULL) {
1138 		uri = g_file_get_uri (file);
1139 		uri_context->uris[uri_context->index++] = uri;
1140 		g_object_unref (file);
1141 
1142 	} else if (error != NULL) {
1143 		/* If this is the first error, cancel the other jobs. */
1144 		if (uri_context->error == NULL) {
1145 			g_propagate_error (&uri_context->error, error);
1146 			g_list_foreach (
1147 				uri_context->attachment_list,
1148 				(GFunc) e_attachment_cancel, NULL);
1149 			error = NULL;
1150 
1151 		/* Otherwise, we can only report back one error.  So if
1152 		 * this is something other than cancellation, dump it to
1153 		 * the terminal. */
1154 		} else if (!g_error_matches (
1155 			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1156 			g_warning ("%s", error->message);
1157 	}
1158 
1159 	if (error != NULL)
1160 		g_error_free (error);
1161 
1162 	/* If there's still jobs running, let them finish. */
1163 	if (uri_context->attachment_list != NULL)
1164 		return;
1165 
1166 	/* Steal the URI list. */
1167 	uris = uri_context->uris;
1168 	uri_context->uris = NULL;
1169 
1170 	/* And the error. */
1171 	error = uri_context->error;
1172 	uri_context->error = NULL;
1173 
1174 	simple = uri_context->simple;
1175 
1176 	if (error == NULL)
1177 		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1178 	else
1179 		g_simple_async_result_take_error (simple, error);
1180 
1181 	g_simple_async_result_complete (simple);
1182 
1183 	attachment_store_uri_context_free (uri_context);
1184 }
1185 
1186 void
e_attachment_store_get_uris_async(EAttachmentStore * store,GList * attachment_list,GAsyncReadyCallback callback,gpointer user_data)1187 e_attachment_store_get_uris_async (EAttachmentStore *store,
1188                                    GList *attachment_list,
1189                                    GAsyncReadyCallback callback,
1190                                    gpointer user_data)
1191 {
1192 	GFile *temp_directory;
1193 	UriContext *uri_context;
1194 	GList *iter, *trash = NULL;
1195 	gchar *template;
1196 	gchar *path;
1197 
1198 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
1199 
1200 	uri_context = attachment_store_uri_context_new (
1201 		store, attachment_list, callback, user_data);
1202 
1203 	/* Grab the copied attachment list. */
1204 	attachment_list = uri_context->attachment_list;
1205 
1206 	/* First scan the list for attachments with a GFile. */
1207 	for (iter = attachment_list; iter != NULL; iter = iter->next) {
1208 		EAttachment *attachment = iter->data;
1209 		GFile *file;
1210 
1211 		file = e_attachment_ref_file (attachment);
1212 		if (file != NULL) {
1213 			gchar *uri;
1214 
1215 			uri = g_file_get_uri (file);
1216 			uri_context->uris[uri_context->index++] = uri;
1217 
1218 			/* Mark the list node for deletion. */
1219 			trash = g_list_prepend (trash, iter);
1220 			g_object_unref (attachment);
1221 
1222 			g_object_unref (file);
1223 		}
1224 	}
1225 
1226 	/* Expunge the list. */
1227 	for (iter = trash; iter != NULL; iter = iter->next) {
1228 		GList *link = iter->data;
1229 		attachment_list = g_list_delete_link (attachment_list, link);
1230 	}
1231 	g_list_free (trash);
1232 
1233 	uri_context->attachment_list = attachment_list;
1234 
1235 	/* If we got them all then we're done. */
1236 	if (attachment_list == NULL) {
1237 		GSimpleAsyncResult *simple;
1238 		gchar **uris;
1239 
1240 		/* Steal the URI list. */
1241 		uris = uri_context->uris;
1242 		uri_context->uris = NULL;
1243 
1244 		simple = uri_context->simple;
1245 		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1246 		g_simple_async_result_complete (simple);
1247 
1248 		attachment_store_uri_context_free (uri_context);
1249 		return;
1250 	}
1251 
1252 	/* Any remaining attachments in the list should have MIME parts
1253 	 * only, so we need to save them all to a temporary directory.
1254 	 * We use a directory so the files can retain their basenames.
1255 	 * XXX This could trigger a blocking temp directory cleanup. */
1256 	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1257 	path = e_mkdtemp (template);
1258 	g_free (template);
1259 
1260 	/* XXX Let's hope errno got set properly. */
1261 	if (path == NULL) {
1262 		GSimpleAsyncResult *simple;
1263 
1264 		simple = uri_context->simple;
1265 		g_simple_async_result_set_error (
1266 			simple, G_FILE_ERROR,
1267 			g_file_error_from_errno (errno),
1268 			"%s", g_strerror (errno));
1269 		g_simple_async_result_complete (simple);
1270 
1271 		attachment_store_uri_context_free (uri_context);
1272 		return;
1273 	}
1274 
1275 	temp_directory = g_file_new_for_path (path);
1276 
1277 	for (iter = attachment_list; iter != NULL; iter = iter->next)
1278 		e_attachment_save_async (
1279 			E_ATTACHMENT (iter->data),
1280 			temp_directory, (GAsyncReadyCallback)
1281 			attachment_store_get_uris_save_cb,
1282 			uri_context);
1283 
1284 	g_object_unref (temp_directory);
1285 	g_free (path);
1286 }
1287 
1288 gchar **
e_attachment_store_get_uris_finish(EAttachmentStore * store,GAsyncResult * result,GError ** error)1289 e_attachment_store_get_uris_finish (EAttachmentStore *store,
1290                                     GAsyncResult *result,
1291                                     GError **error)
1292 {
1293 	GSimpleAsyncResult *simple;
1294 	gchar **uris;
1295 
1296 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
1297 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
1298 
1299 	simple = G_SIMPLE_ASYNC_RESULT (result);
1300 	if (g_simple_async_result_propagate_error (simple, error))
1301 		return NULL;
1302 
1303 	uris = g_simple_async_result_get_op_res_gpointer (simple);
1304 
1305 	return uris;
1306 }
1307 
1308 /********************** e_attachment_store_load_async() **********************/
1309 
1310 typedef struct _LoadContext LoadContext;
1311 
1312 struct _LoadContext {
1313 	GSimpleAsyncResult *simple;
1314 	GList *attachment_list;
1315 	GError *error;
1316 };
1317 
1318 static LoadContext *
attachment_store_load_context_new(EAttachmentStore * store,GList * attachment_list,GAsyncReadyCallback callback,gpointer user_data)1319 attachment_store_load_context_new (EAttachmentStore *store,
1320                                    GList *attachment_list,
1321                                    GAsyncReadyCallback callback,
1322                                    gpointer user_data)
1323 {
1324 	LoadContext *load_context;
1325 	GSimpleAsyncResult *simple;
1326 
1327 	simple = g_simple_async_result_new (
1328 		G_OBJECT (store), callback, user_data,
1329 		e_attachment_store_load_async);
1330 
1331 	load_context = g_slice_new0 (LoadContext);
1332 	load_context->simple = simple;
1333 	load_context->attachment_list = g_list_copy (attachment_list);
1334 
1335 	g_list_foreach (
1336 		load_context->attachment_list,
1337 		(GFunc) g_object_ref, NULL);
1338 
1339 	return load_context;
1340 }
1341 
1342 static void
attachment_store_load_context_free(LoadContext * load_context)1343 attachment_store_load_context_free (LoadContext *load_context)
1344 {
1345 	g_object_unref (load_context->simple);
1346 
1347 	/* The attachment list should be empty now. */
1348 	g_warn_if_fail (load_context->attachment_list == NULL);
1349 
1350 	/* So should the error. */
1351 	g_warn_if_fail (load_context->error == NULL);
1352 
1353 	g_slice_free (LoadContext, load_context);
1354 }
1355 
1356 static void
attachment_store_load_ready_cb(EAttachment * attachment,GAsyncResult * result,LoadContext * load_context)1357 attachment_store_load_ready_cb (EAttachment *attachment,
1358                                 GAsyncResult *result,
1359                                 LoadContext *load_context)
1360 {
1361 	GSimpleAsyncResult *simple;
1362 	GError *error = NULL;
1363 
1364 	e_attachment_load_finish (attachment, result, &error);
1365 
1366 	/* Remove the attachment from the list. */
1367 	load_context->attachment_list = g_list_remove (
1368 		load_context->attachment_list, attachment);
1369 	g_object_unref (attachment);
1370 
1371 	if (error != NULL) {
1372 		/* If this is the first error, cancel the other jobs. */
1373 		if (load_context->error == NULL) {
1374 			g_propagate_error (&load_context->error, error);
1375 			g_list_foreach (
1376 				load_context->attachment_list,
1377 				(GFunc) e_attachment_cancel, NULL);
1378 			error = NULL;
1379 
1380 		/* Otherwise, we can only report back one error.  So if
1381 		 * this is something other than cancellation, dump it to
1382 		 * the terminal. */
1383 		} else if (!g_error_matches (
1384 			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1385 			g_warning ("%s", error->message);
1386 	}
1387 
1388 	if (error != NULL)
1389 		g_error_free (error);
1390 
1391 	/* If there's still jobs running, let them finish. */
1392 	if (load_context->attachment_list != NULL)
1393 		return;
1394 
1395 	/* Steal the error. */
1396 	error = load_context->error;
1397 	load_context->error = NULL;
1398 
1399 	simple = load_context->simple;
1400 
1401 	if (error == NULL)
1402 		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
1403 	else
1404 		g_simple_async_result_take_error (simple, error);
1405 
1406 	g_simple_async_result_complete (simple);
1407 
1408 	attachment_store_load_context_free (load_context);
1409 }
1410 
1411 void
e_attachment_store_load_async(EAttachmentStore * store,GList * attachment_list,GAsyncReadyCallback callback,gpointer user_data)1412 e_attachment_store_load_async (EAttachmentStore *store,
1413                                GList *attachment_list,
1414                                GAsyncReadyCallback callback,
1415                                gpointer user_data)
1416 {
1417 	LoadContext *load_context;
1418 	GList *iter;
1419 
1420 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
1421 
1422 	load_context = attachment_store_load_context_new (
1423 		store, attachment_list, callback, user_data);
1424 
1425 	if (attachment_list == NULL) {
1426 		GSimpleAsyncResult *simple;
1427 
1428 		simple = load_context->simple;
1429 		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
1430 		g_simple_async_result_complete (simple);
1431 
1432 		attachment_store_load_context_free (load_context);
1433 		return;
1434 	}
1435 
1436 	for (iter = attachment_list; iter != NULL; iter = iter->next) {
1437 		EAttachment *attachment = E_ATTACHMENT (iter->data);
1438 
1439 		e_attachment_store_add_attachment (store, attachment);
1440 
1441 		e_attachment_load_async (
1442 			attachment, (GAsyncReadyCallback)
1443 			attachment_store_load_ready_cb,
1444 			load_context);
1445 	}
1446 }
1447 
1448 gboolean
e_attachment_store_load_finish(EAttachmentStore * store,GAsyncResult * result,GError ** error)1449 e_attachment_store_load_finish (EAttachmentStore *store,
1450                                 GAsyncResult *result,
1451                                 GError **error)
1452 {
1453 	GSimpleAsyncResult *simple;
1454 	gboolean success;
1455 
1456 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
1457 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
1458 
1459 	simple = G_SIMPLE_ASYNC_RESULT (result);
1460 	success = !g_simple_async_result_propagate_error (simple, error) &&
1461 		   g_simple_async_result_get_op_res_gboolean (simple);
1462 
1463 	return success;
1464 }
1465 
1466 /********************** e_attachment_store_save_async() **********************/
1467 
1468 typedef struct _SaveContext SaveContext;
1469 
1470 struct _SaveContext {
1471 	GSimpleAsyncResult *simple;
1472 	GFile *destination;
1473 	gchar *filename_prefix;
1474 	GFile *fresh_directory;
1475 	GFile *trash_directory;
1476 	GList *attachment_list;
1477 	GError *error;
1478 	gchar **uris;
1479 	gint index;
1480 };
1481 
1482 static SaveContext *
attachment_store_save_context_new(EAttachmentStore * store,GFile * destination,const gchar * filename_prefix,GAsyncReadyCallback callback,gpointer user_data)1483 attachment_store_save_context_new (EAttachmentStore *store,
1484                                    GFile *destination,
1485                                    const gchar *filename_prefix,
1486                                    GAsyncReadyCallback callback,
1487                                    gpointer user_data)
1488 {
1489 	SaveContext *save_context;
1490 	GSimpleAsyncResult *simple;
1491 	GList *attachment_list;
1492 	guint length;
1493 	gchar **uris;
1494 
1495 	simple = g_simple_async_result_new (
1496 		G_OBJECT (store), callback, user_data,
1497 		e_attachment_store_save_async);
1498 
1499 	attachment_list = e_attachment_store_get_attachments (store);
1500 
1501 	/* Add one for NULL terminator. */
1502 	length = g_list_length (attachment_list) + 1;
1503 	uris = g_malloc0 (sizeof (gchar *) * length);
1504 
1505 	save_context = g_slice_new0 (SaveContext);
1506 	save_context->simple = simple;
1507 	save_context->destination = g_object_ref (destination);
1508 	save_context->filename_prefix = g_strdup (filename_prefix);
1509 	save_context->attachment_list = attachment_list;
1510 	save_context->uris = uris;
1511 
1512 	return save_context;
1513 }
1514 
1515 static void
attachment_store_save_context_free(SaveContext * save_context)1516 attachment_store_save_context_free (SaveContext *save_context)
1517 {
1518 	g_object_unref (save_context->simple);
1519 
1520 	/* The attachment list should be empty now. */
1521 	g_warn_if_fail (save_context->attachment_list == NULL);
1522 
1523 	/* So should the error. */
1524 	g_warn_if_fail (save_context->error == NULL);
1525 
1526 	g_clear_object (&save_context->destination);
1527 	g_clear_object (&save_context->fresh_directory);
1528 	g_clear_object (&save_context->trash_directory);
1529 	g_clear_pointer (&save_context->filename_prefix, g_free);
1530 	g_strfreev (save_context->uris);
1531 	g_slice_free (SaveContext, save_context);
1532 }
1533 
1534 static void
attachment_store_move_file(SaveContext * save_context,GFile * source,GFile * destination,GError ** error)1535 attachment_store_move_file (SaveContext *save_context,
1536                             GFile *source,
1537                             GFile *destination,
1538                             GError **error)
1539 {
1540 	gchar *tmpl;
1541 	gchar *path;
1542 	GError *local_error = NULL;
1543 
1544 	g_return_if_fail (save_context != NULL);
1545 	g_return_if_fail (source != NULL);
1546 	g_return_if_fail (destination != NULL);
1547 	g_return_if_fail (error != NULL);
1548 
1549 	/* Attachments are all saved to a temporary directory.
1550 	 * Now we need to move the existing destination directory
1551 	 * out of the way (if it exists).  Instead of testing for
1552 	 * existence we'll just attempt the move and ignore any
1553 	 * G_IO_ERROR_NOT_FOUND errors. */
1554 
1555 	/* First, however, we need another temporary directory to
1556 	 * move the existing destination directory to.  Note we're
1557 	 * not actually creating the directory yet, just picking a
1558 	 * name for it.  The usual raciness with this approach
1559 	 * applies here (read up on mktemp(3)), but worst case is
1560 	 * we get a spurious G_IO_ERROR_WOULD_MERGE error and the
1561 	 * user has to try saving attachments again. */
1562 	tmpl = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1563 	path = e_mktemp (tmpl);
1564 	g_free (tmpl);
1565 
1566 	save_context->trash_directory = g_file_new_for_path (path);
1567 	g_free (path);
1568 
1569 	/* XXX No asynchronous move operation in GIO? */
1570 	g_file_move (
1571 		destination,
1572 		save_context->trash_directory,
1573 		G_FILE_COPY_NONE, NULL, NULL,
1574 		NULL, &local_error);
1575 
1576 	if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
1577 		g_clear_error (&local_error);
1578 
1579 	if (local_error != NULL) {
1580 		g_propagate_error (error, local_error);
1581 		return;
1582 	}
1583 
1584 	/* Now we can move the file from the temporary directory
1585 	 * to the user-specified destination. */
1586 	g_file_move (
1587 		source,
1588 		destination,
1589 		G_FILE_COPY_NONE, NULL, NULL, NULL, error);
1590 }
1591 
1592 static void
attachment_store_save_cb(EAttachment * attachment,GAsyncResult * result,SaveContext * save_context)1593 attachment_store_save_cb (EAttachment *attachment,
1594                           GAsyncResult *result,
1595                           SaveContext *save_context)
1596 {
1597 	GSimpleAsyncResult *simple;
1598 	GFile *file;
1599 	gchar **uris;
1600 	GError *error = NULL;
1601 
1602 	file = e_attachment_save_finish (attachment, result, &error);
1603 
1604 	/* Remove the attachment from the list. */
1605 	save_context->attachment_list = g_list_remove (
1606 		save_context->attachment_list, attachment);
1607 	g_object_unref (attachment);
1608 
1609 	if (file != NULL) {
1610 		/* Assemble the file's final URI from its basename. */
1611 		GFile *source = NULL;
1612 		GFile *destination = NULL;
1613 		gchar *basename;
1614 		gchar *uri;
1615 		const gchar *prefix;
1616 
1617 		basename = g_file_get_basename (file);
1618 		g_object_unref (file);
1619 
1620 		source = g_file_get_child (
1621 			save_context->fresh_directory, basename);
1622 
1623 		prefix = save_context->filename_prefix;
1624 
1625 		if (prefix != NULL && *prefix != '\0') {
1626 			gchar *tmp = basename;
1627 			basename = g_strconcat (prefix, basename, NULL);
1628 			g_free (tmp);
1629 		}
1630 
1631 		file = save_context->destination;
1632 		destination = g_file_get_child (file, basename);
1633 		uri = g_file_get_uri (destination);
1634 
1635 		/* move them file-by-file */
1636 		attachment_store_move_file (
1637 			save_context, source, destination, &error);
1638 
1639 		if (error == NULL)
1640 			save_context->uris[save_context->index++] = uri;
1641 
1642 		g_object_unref (source);
1643 		g_object_unref (destination);
1644 	}
1645 
1646 	if (error != NULL) {
1647 		/* If this is the first error, cancel the other jobs. */
1648 		if (save_context->error == NULL) {
1649 			g_propagate_error (&save_context->error, error);
1650 			g_list_foreach (
1651 				save_context->attachment_list,
1652 				(GFunc) e_attachment_cancel, NULL);
1653 			error = NULL;
1654 
1655 		/* Otherwise, we can only report back one error.  So if
1656 		 * this is something other than cancellation, dump it to
1657 		 * the terminal. */
1658 		} else if (!g_error_matches (
1659 			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1660 			g_warning ("%s", error->message);
1661 	}
1662 
1663 	g_clear_error (&error);
1664 
1665 	/* If there's still jobs running, let them finish. */
1666 	if (save_context->attachment_list != NULL)
1667 		return;
1668 
1669 	/* If an error occurred while saving, we're done. */
1670 	if (save_context->error != NULL) {
1671 
1672 		/* Steal the error. */
1673 		error = save_context->error;
1674 		save_context->error = NULL;
1675 
1676 		simple = save_context->simple;
1677 		g_simple_async_result_take_error (simple, error);
1678 		g_simple_async_result_complete (simple);
1679 
1680 		attachment_store_save_context_free (save_context);
1681 		return;
1682 	}
1683 
1684 	if (error != NULL) {
1685 		simple = save_context->simple;
1686 		g_simple_async_result_take_error (simple, error);
1687 		g_simple_async_result_complete (simple);
1688 
1689 		attachment_store_save_context_free (save_context);
1690 		return;
1691 	}
1692 
1693 	/* clean-up left directory */
1694 	g_file_delete (save_context->fresh_directory, NULL, NULL);
1695 
1696 	/* And the URI list. */
1697 	uris = save_context->uris;
1698 	save_context->uris = NULL;
1699 
1700 	simple = save_context->simple;
1701 	g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1702 	g_simple_async_result_complete (simple);
1703 
1704 	attachment_store_save_context_free (save_context);
1705 }
1706 /*
1707  * @filename_prefix: prefix to use for a file name; can be %NULL for none
1708  **/
1709 void
e_attachment_store_save_async(EAttachmentStore * store,GFile * destination,const gchar * filename_prefix,GAsyncReadyCallback callback,gpointer user_data)1710 e_attachment_store_save_async (EAttachmentStore *store,
1711                                GFile *destination,
1712                                const gchar *filename_prefix,
1713                                GAsyncReadyCallback callback,
1714                                gpointer user_data)
1715 {
1716 	SaveContext *save_context;
1717 	GList *attachment_list, *iter;
1718 	GFile *temp_directory;
1719 	gchar *template;
1720 	gchar *path;
1721 
1722 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
1723 	g_return_if_fail (G_IS_FILE (destination));
1724 
1725 	save_context = attachment_store_save_context_new (
1726 		store, destination, filename_prefix, callback, user_data);
1727 
1728 	attachment_list = save_context->attachment_list;
1729 
1730 	/* Deal with an empty attachment store.  The caller will get
1731 	 * an empty NULL-terminated list as opposed to NULL, to help
1732 	 * distinguish it from an error. */
1733 	if (attachment_list == NULL) {
1734 		GSimpleAsyncResult *simple;
1735 		gchar **uris;
1736 
1737 		/* Steal the URI list. */
1738 		uris = save_context->uris;
1739 		save_context->uris = NULL;
1740 
1741 		simple = save_context->simple;
1742 		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1743 		g_simple_async_result_complete (simple);
1744 
1745 		attachment_store_save_context_free (save_context);
1746 		return;
1747 	}
1748 
1749 	/* Save all attachments to a temporary directory, which we'll
1750 	 * then move to its proper location.  We use a directory so
1751 	 * files can retain their basenames.
1752 	 * XXX This could trigger a blocking temp directory cleanup. */
1753 	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1754 	path = e_mkdtemp (template);
1755 	g_free (template);
1756 
1757 	/* XXX Let's hope errno got set properly. */
1758 	if (path == NULL) {
1759 		GSimpleAsyncResult *simple;
1760 
1761 		simple = save_context->simple;
1762 		g_simple_async_result_set_error (
1763 			simple, G_FILE_ERROR,
1764 			g_file_error_from_errno (errno),
1765 			"%s", g_strerror (errno));
1766 		g_simple_async_result_complete (simple);
1767 
1768 		attachment_store_save_context_free (save_context);
1769 		return;
1770 	}
1771 
1772 	temp_directory = g_file_new_for_path (path);
1773 	save_context->fresh_directory = temp_directory;
1774 	g_free (path);
1775 
1776 	for (iter = attachment_list; iter != NULL; iter = iter->next)
1777 		e_attachment_save_async (
1778 			E_ATTACHMENT (iter->data),
1779 			temp_directory, (GAsyncReadyCallback)
1780 			attachment_store_save_cb, save_context);
1781 }
1782 
1783 gchar **
e_attachment_store_save_finish(EAttachmentStore * store,GAsyncResult * result,GError ** error)1784 e_attachment_store_save_finish (EAttachmentStore *store,
1785                                 GAsyncResult *result,
1786                                 GError **error)
1787 {
1788 	GSimpleAsyncResult *simple;
1789 	gchar **uris;
1790 
1791 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
1792 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
1793 
1794 	simple = G_SIMPLE_ASYNC_RESULT (result);
1795 	if (g_simple_async_result_propagate_error (simple, error))
1796 		return NULL;
1797 
1798 	uris = g_simple_async_result_get_op_res_gpointer (simple);
1799 
1800 	return uris;
1801 }
1802