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