No issues found
1 /*
2 * e-attachment-store.c
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 *
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19 *
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "e-attachment-store.h"
27
28 #include <errno.h>
29 #include <glib/gi18n.h>
30
31 #include "e-util/e-util.h"
32 #include "e-util/e-mktemp.h"
33
34 #define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
35 (G_TYPE_INSTANCE_GET_PRIVATE \
36 ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))
37
38 struct _EAttachmentStorePrivate {
39 GHashTable *attachment_index;
40
41 guint ignore_row_changed : 1;
42 };
43
44 enum {
45 PROP_0,
46 PROP_NUM_ATTACHMENTS,
47 PROP_NUM_LOADING,
48 PROP_TOTAL_SIZE
49 };
50
51 G_DEFINE_TYPE (
52 EAttachmentStore,
53 e_attachment_store,
54 GTK_TYPE_LIST_STORE)
55
56 static void
57 attachment_store_get_property (GObject *object,
58 guint property_id,
59 GValue *value,
60 GParamSpec *pspec)
61 {
62 switch (property_id) {
63 case PROP_NUM_ATTACHMENTS:
64 g_value_set_uint (
65 value,
66 e_attachment_store_get_num_attachments (
67 E_ATTACHMENT_STORE (object)));
68 return;
69
70 case PROP_NUM_LOADING:
71 g_value_set_uint (
72 value,
73 e_attachment_store_get_num_loading (
74 E_ATTACHMENT_STORE (object)));
75 return;
76
77 case PROP_TOTAL_SIZE:
78 g_value_set_uint64 (
79 value,
80 e_attachment_store_get_total_size (
81 E_ATTACHMENT_STORE (object)));
82 return;
83 }
84
85 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
86 }
87
88 static void
89 attachment_store_dispose (GObject *object)
90 {
91 e_attachment_store_remove_all (E_ATTACHMENT_STORE (object));
92
93 /* Chain up to parent's dispose() method. */
94 G_OBJECT_CLASS (e_attachment_store_parent_class)->dispose (object);
95 }
96
97 static void
98 attachment_store_finalize (GObject *object)
99 {
100 EAttachmentStorePrivate *priv;
101
102 priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
103
104 g_hash_table_destroy (priv->attachment_index);
105
106 /* Chain up to parent's finalize() method. */
107 G_OBJECT_CLASS (e_attachment_store_parent_class)->finalize (object);
108 }
109
110 static void
111 e_attachment_store_class_init (EAttachmentStoreClass *class)
112 {
113 GObjectClass *object_class;
114
115 g_type_class_add_private (class, sizeof (EAttachmentStorePrivate));
116
117 object_class = G_OBJECT_CLASS (class);
118 object_class->get_property = attachment_store_get_property;
119 object_class->dispose = attachment_store_dispose;
120 object_class->finalize = attachment_store_finalize;
121
122 g_object_class_install_property (
123 object_class,
124 PROP_NUM_ATTACHMENTS,
125 g_param_spec_uint (
126 "num-attachments",
127 "Num Attachments",
128 NULL,
129 0,
130 G_MAXUINT,
131 0,
132 G_PARAM_READABLE));
133
134 g_object_class_install_property (
135 object_class,
136 PROP_NUM_LOADING,
137 g_param_spec_uint (
138 "num-loading",
139 "Num Loading",
140 NULL,
141 0,
142 G_MAXUINT,
143 0,
144 G_PARAM_READABLE));
145
146 g_object_class_install_property (
147 object_class,
148 PROP_TOTAL_SIZE,
149 g_param_spec_uint64 (
150 "total-size",
151 "Total Size",
152 NULL,
153 0,
154 G_MAXUINT64,
155 0,
156 G_PARAM_READABLE));
157 }
158
159 static void
160 e_attachment_store_init (EAttachmentStore *store)
161 {
162 GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
163 GHashTable *attachment_index;
164 gint column = 0;
165
166 attachment_index = g_hash_table_new_full (
167 g_direct_hash, g_direct_equal,
168 (GDestroyNotify) g_object_unref,
169 (GDestroyNotify) gtk_tree_row_reference_free);
170
171 store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
172 store->priv->attachment_index = attachment_index;
173
174 types[column++] = E_TYPE_ATTACHMENT; /* COLUMN_ATTACHMENT */
175 types[column++] = G_TYPE_STRING; /* COLUMN_CAPTION */
176 types[column++] = G_TYPE_STRING; /* COLUMN_CONTENT_TYPE */
177 types[column++] = G_TYPE_STRING; /* COLUMN_DESCRIPTION */
178 types[column++] = G_TYPE_ICON; /* COLUMN_ICON */
179 types[column++] = G_TYPE_BOOLEAN; /* COLUMN_LOADING */
180 types[column++] = G_TYPE_INT; /* COLUMN_PERCENT */
181 types[column++] = G_TYPE_BOOLEAN; /* COLUMN_SAVING */
182 types[column++] = G_TYPE_UINT64; /* COLUMN_SIZE */
183
184 g_assert (column == E_ATTACHMENT_STORE_NUM_COLUMNS);
185
186 gtk_list_store_set_column_types (
187 GTK_LIST_STORE (store), G_N_ELEMENTS (types), types);
188 }
189
190 GtkTreeModel *
191 e_attachment_store_new (void)
192 {
193 return g_object_new (E_TYPE_ATTACHMENT_STORE, NULL);
194 }
195
196 void
197 e_attachment_store_add_attachment (EAttachmentStore *store,
198 EAttachment *attachment)
199 {
200 GtkTreeRowReference *reference;
201 GtkTreeModel *model;
202 GtkTreePath *path;
203 GtkTreeIter iter;
204
205 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
206 g_return_if_fail (E_IS_ATTACHMENT (attachment));
207
208 gtk_list_store_append (GTK_LIST_STORE (store), &iter);
209
210 gtk_list_store_set (
211 GTK_LIST_STORE (store), &iter,
212 E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, attachment, -1);
213
214 model = GTK_TREE_MODEL (store);
215 path = gtk_tree_model_get_path (model, &iter);
216 reference = gtk_tree_row_reference_new (model, path);
217 gtk_tree_path_free (path);
218
219 g_hash_table_insert (
220 store->priv->attachment_index,
221 g_object_ref (attachment), reference);
222
223 /* This lets the attachment tell us when to update. */
224 e_attachment_set_reference (attachment, reference);
225
226 g_object_freeze_notify (G_OBJECT (store));
227 g_object_notify (G_OBJECT (store), "num-attachments");
228 g_object_notify (G_OBJECT (store), "total-size");
229 g_object_thaw_notify (G_OBJECT (store));
230 }
231
232 gboolean
233 e_attachment_store_remove_attachment (EAttachmentStore *store,
234 EAttachment *attachment)
235 {
236 GtkTreeRowReference *reference;
237 GHashTable *hash_table;
238 GtkTreeModel *model;
239 GtkTreePath *path;
240 GtkTreeIter iter;
241
242 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
243 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
244
245 hash_table = store->priv->attachment_index;
246 reference = g_hash_table_lookup (hash_table, attachment);
247
248 if (reference == NULL)
249 return FALSE;
250
251 if (!gtk_tree_row_reference_valid (reference)) {
252 g_hash_table_remove (hash_table, attachment);
253 return FALSE;
254 }
255
256 e_attachment_cancel (attachment);
257 e_attachment_set_reference (attachment, NULL);
258
259 model = gtk_tree_row_reference_get_model (reference);
260 path = gtk_tree_row_reference_get_path (reference);
261 gtk_tree_model_get_iter (model, &iter, path);
262 gtk_tree_path_free (path);
263
264 gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
265 g_hash_table_remove (hash_table, attachment);
266
267 g_object_freeze_notify (G_OBJECT (store));
268 g_object_notify (G_OBJECT (store), "num-attachments");
269 g_object_notify (G_OBJECT (store), "total-size");
270 g_object_thaw_notify (G_OBJECT (store));
271
272 return TRUE;
273 }
274
275 void
276 e_attachment_store_remove_all (EAttachmentStore *store)
277 {
278 GList *list, *iter;
279
280 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
281
282 if (!g_hash_table_size (store->priv->attachment_index))
283 return;
284
285 g_object_freeze_notify (G_OBJECT (store));
286
287 list = e_attachment_store_get_attachments (store);
288 for (iter = list; iter; iter = iter->next) {
289 EAttachment *attachment = iter->data;
290
291 e_attachment_cancel (attachment);
292 g_hash_table_remove (store->priv->attachment_index, iter->data);
293 }
294
295 g_list_foreach (list, (GFunc) g_object_unref, NULL);
296 g_list_free (list);
297
298 gtk_list_store_clear (GTK_LIST_STORE (store));
299
300 g_object_notify (G_OBJECT (store), "num-attachments");
301 g_object_notify (G_OBJECT (store), "total-size");
302 g_object_thaw_notify (G_OBJECT (store));
303 }
304
305 void
306 e_attachment_store_add_to_multipart (EAttachmentStore *store,
307 CamelMultipart *multipart,
308 const gchar *default_charset)
309 {
310 GList *list, *iter;
311
312 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
313 g_return_if_fail (CAMEL_MULTIPART (multipart));
314
315 list = e_attachment_store_get_attachments (store);
316
317 for (iter = list; iter != NULL; iter = iter->next) {
318 EAttachment *attachment = iter->data;
319
320 /* Skip the attachment if it's still loading. */
321 if (!e_attachment_get_loading (attachment))
322 e_attachment_add_to_multipart (
323 attachment, multipart, default_charset);
324 }
325
326 g_list_foreach (list, (GFunc) g_object_unref, NULL);
327 g_list_free (list);
328 }
329
330 GList *
331 e_attachment_store_get_attachments (EAttachmentStore *store)
332 {
333 GList *list = NULL;
334 GtkTreeModel *model;
335 GtkTreeIter iter;
336 gboolean valid;
337
338 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
339
340 model = GTK_TREE_MODEL (store);
341 valid = gtk_tree_model_get_iter_first (model, &iter);
342
343 while (valid) {
344 EAttachment *attachment;
345 gint column_id;
346
347 column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
348 gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
349
350 list = g_list_prepend (list, attachment);
351
352 valid = gtk_tree_model_iter_next (model, &iter);
353 }
354
355 return g_list_reverse (list);
356 }
357
358 guint
359 e_attachment_store_get_num_attachments (EAttachmentStore *store)
360 {
361 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
362
363 return g_hash_table_size (store->priv->attachment_index);
364 }
365
366 guint
367 e_attachment_store_get_num_loading (EAttachmentStore *store)
368 {
369 GList *list, *iter;
370 guint num_loading = 0;
371
372 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
373
374 list = e_attachment_store_get_attachments (store);
375
376 for (iter = list; iter != NULL; iter = iter->next) {
377 EAttachment *attachment = iter->data;
378
379 if (e_attachment_get_loading (attachment))
380 num_loading++;
381 }
382
383 g_list_foreach (list, (GFunc) g_object_unref, NULL);
384 g_list_free (list);
385
386 return num_loading;
387 }
388
389 goffset
390 e_attachment_store_get_total_size (EAttachmentStore *store)
391 {
392 GList *list, *iter;
393 goffset total_size = 0;
394
395 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
396
397 list = e_attachment_store_get_attachments (store);
398
399 for (iter = list; iter != NULL; iter = iter->next) {
400 EAttachment *attachment = iter->data;
401 GFileInfo *file_info;
402
403 file_info = e_attachment_get_file_info (attachment);
404 if (file_info != NULL)
405 total_size += g_file_info_get_size (file_info);
406 }
407
408 g_list_foreach (list, (GFunc) g_object_unref, NULL);
409 g_list_free (list);
410
411 return total_size;
412 }
413
414 void
415 e_attachment_store_run_load_dialog (EAttachmentStore *store,
416 GtkWindow *parent)
417 {
418 GtkFileChooser *file_chooser;
419 GtkWidget *dialog;
420 GtkWidget *option;
421 GSList *files, *iter;
422 const gchar *disposition;
423 gboolean active;
424 gint response;
425
426 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
427 g_return_if_fail (GTK_IS_WINDOW (parent));
428
429 dialog = gtk_file_chooser_dialog_new (
430 _("Add Attachment"), parent,
431 GTK_FILE_CHOOSER_ACTION_OPEN,
432 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
433 _("A_ttach"), GTK_RESPONSE_OK, NULL);
434
435 file_chooser = GTK_FILE_CHOOSER (dialog);
436 gtk_file_chooser_set_local_only (file_chooser, FALSE);
437 gtk_file_chooser_set_select_multiple (file_chooser, TRUE);
438 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
439 gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
440
441 option = gtk_check_button_new_with_mnemonic (
442 _("_Suggest automatic display of attachment"));
443 gtk_file_chooser_set_extra_widget (file_chooser, option);
444 gtk_widget_show (option);
445
446 response = gtk_dialog_run (GTK_DIALOG (dialog));
447
448 if (response != GTK_RESPONSE_OK)
449 goto exit;
450
451 files = gtk_file_chooser_get_files (file_chooser);
452 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (option));
453 disposition = active ? "inline" : "attachment";
454
455 for (iter = files; iter != NULL; iter = g_slist_next (iter)) {
456 EAttachment *attachment;
457 GFile *file = iter->data;
458
459 attachment = e_attachment_new ();
460 e_attachment_set_file (attachment, file);
461 e_attachment_set_disposition (attachment, disposition);
462 e_attachment_store_add_attachment (store, attachment);
463 e_attachment_load_async (
464 attachment, (GAsyncReadyCallback)
465 e_attachment_load_handle_error, parent);
466 g_object_unref (attachment);
467 }
468
469 g_slist_foreach (files, (GFunc) g_object_unref, NULL);
470 g_slist_free (files);
471
472 exit:
473 gtk_widget_destroy (dialog);
474 }
475
476 GFile *
477 e_attachment_store_run_save_dialog (EAttachmentStore *store,
478 GList *attachment_list,
479 GtkWindow *parent)
480 {
481 GtkFileChooser *file_chooser;
482 GtkFileChooserAction action;
483 GtkWidget *dialog;
484 GFile *destination;
485 const gchar *title;
486 gint response;
487 guint length;
488
489 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
490
491 length = g_list_length (attachment_list);
492
493 if (length == 0)
494 return NULL;
495
496 title = ngettext ("Save Attachment", "Save Attachments", length);
497
498 if (length == 1)
499 action = GTK_FILE_CHOOSER_ACTION_SAVE;
500 else
501 action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
502
503 dialog = gtk_file_chooser_dialog_new (
504 title, parent, action,
505 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
506 GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL);
507
508 file_chooser = GTK_FILE_CHOOSER (dialog);
509 gtk_file_chooser_set_local_only (file_chooser, FALSE);
510 gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
511 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
512 gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
513
514 if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
515 EAttachment *attachment;
516 GFileInfo *file_info;
517 const gchar *name = NULL;
518
519 attachment = attachment_list->data;
520 file_info = e_attachment_get_file_info (attachment);
521 if (file_info != NULL)
522 name = g_file_info_get_display_name (file_info);
523 if (name == NULL)
524 /* Translators: Default attachment filename. */
525 name = _("attachment.dat");
526 gtk_file_chooser_set_current_name (file_chooser, name);
527 }
528
529 response = gtk_dialog_run (GTK_DIALOG (dialog));
530
531 if (response == GTK_RESPONSE_OK)
532 destination = gtk_file_chooser_get_file (file_chooser);
533 else
534 destination = NULL;
535
536 gtk_widget_destroy (dialog);
537
538 return destination;
539 }
540
541 /******************** e_attachment_store_get_uris_async() ********************/
542
543 typedef struct _UriContext UriContext;
544
545 struct _UriContext {
546 GSimpleAsyncResult *simple;
547 GList *attachment_list;
548 GError *error;
549 gchar **uris;
550 gint index;
551 };
552
553 static UriContext *
554 attachment_store_uri_context_new (EAttachmentStore *store,
555 GList *attachment_list,
556 GAsyncReadyCallback callback,
557 gpointer user_data)
558 {
559 UriContext *uri_context;
560 GSimpleAsyncResult *simple;
561 guint length;
562 gchar **uris;
563
564 simple = g_simple_async_result_new (
565 G_OBJECT (store), callback, user_data,
566 e_attachment_store_get_uris_async);
567
568 /* Add one for NULL terminator. */
569 length = g_list_length (attachment_list) + 1;
570 uris = g_malloc0 (sizeof (gchar *) * length);
571
572 uri_context = g_slice_new0 (UriContext);
573 uri_context->simple = simple;
574 uri_context->attachment_list = g_list_copy (attachment_list);
575 uri_context->uris = uris;
576
577 g_list_foreach (
578 uri_context->attachment_list,
579 (GFunc) g_object_ref, NULL);
580
581 return uri_context;
582 }
583
584 static void
585 attachment_store_uri_context_free (UriContext *uri_context)
586 {
587 g_object_unref (uri_context->simple);
588
589 /* The attachment list should be empty now. */
590 g_warn_if_fail (uri_context->attachment_list == NULL);
591
592 /* So should the error. */
593 g_warn_if_fail (uri_context->error == NULL);
594
595 g_strfreev (uri_context->uris);
596
597 g_slice_free (UriContext, uri_context);
598 }
599
600 static void
601 attachment_store_get_uris_save_cb (EAttachment *attachment,
602 GAsyncResult *result,
603 UriContext *uri_context)
604 {
605 GSimpleAsyncResult *simple;
606 GFile *file;
607 gchar **uris;
608 gchar *uri;
609 GError *error = NULL;
610
611 file = e_attachment_save_finish (attachment, result, &error);
612
613 /* Remove the attachment from the list. */
614 uri_context->attachment_list = g_list_remove (
615 uri_context->attachment_list, attachment);
616 g_object_unref (attachment);
617
618 if (file != NULL) {
619 uri = g_file_get_uri (file);
620 uri_context->uris[uri_context->index++] = uri;
621 g_object_unref (file);
622
623 } else if (error != NULL) {
624 /* If this is the first error, cancel the other jobs. */
625 if (uri_context->error == NULL) {
626 g_propagate_error (&uri_context->error, error);
627 g_list_foreach (
628 uri_context->attachment_list,
629 (GFunc) e_attachment_cancel, NULL);
630 error = NULL;
631
632 /* Otherwise, we can only report back one error. So if
633 * this is something other than cancellation, dump it to
634 * the terminal. */
635 } else if (!g_error_matches (
636 error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
637 g_warning ("%s", error->message);
638 }
639
640 if (error != NULL)
641 g_error_free (error);
642
643 /* If there's still jobs running, let them finish. */
644 if (uri_context->attachment_list != NULL)
645 return;
646
647 /* Steal the URI list. */
648 uris = uri_context->uris;
649 uri_context->uris = NULL;
650
651 /* And the error. */
652 error = uri_context->error;
653 uri_context->error = NULL;
654
655 simple = uri_context->simple;
656
657 if (error == NULL)
658 g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
659 else
660 g_simple_async_result_take_error (simple, error);
661
662 g_simple_async_result_complete (simple);
663
664 attachment_store_uri_context_free (uri_context);
665 }
666
667 void
668 e_attachment_store_get_uris_async (EAttachmentStore *store,
669 GList *attachment_list,
670 GAsyncReadyCallback callback,
671 gpointer user_data)
672 {
673 GFile *temp_directory;
674 UriContext *uri_context;
675 GList *iter, *trash = NULL;
676 gchar *template;
677 gchar *path;
678
679 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
680
681 uri_context = attachment_store_uri_context_new (
682 store, attachment_list, callback, user_data);
683
684 /* Grab the copied attachment list. */
685 attachment_list = uri_context->attachment_list;
686
687 /* First scan the list for attachments with a GFile. */
688 for (iter = attachment_list; iter != NULL; iter = iter->next) {
689 EAttachment *attachment = iter->data;
690 GFile *file;
691 gchar *uri;
692
693 file = e_attachment_get_file (attachment);
694 if (file == NULL)
695 continue;
696
697 uri = g_file_get_uri (file);
698 uri_context->uris[uri_context->index++] = uri;
699
700 /* Mark the list node for deletion. */
701 trash = g_list_prepend (trash, iter);
702 g_object_unref (attachment);
703 }
704
705 /* Expunge the list. */
706 for (iter = trash; iter != NULL; iter = iter->next) {
707 GList *link = iter->data;
708 attachment_list = g_list_delete_link (attachment_list, link);
709 }
710 g_list_free (trash);
711
712 uri_context->attachment_list = attachment_list;
713
714 /* If we got them all then we're done. */
715 if (attachment_list == NULL) {
716 GSimpleAsyncResult *simple;
717 gchar **uris;
718
719 /* Steal the URI list. */
720 uris = uri_context->uris;
721 uri_context->uris = NULL;
722
723 simple = uri_context->simple;
724 g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
725 g_simple_async_result_complete (simple);
726
727 attachment_store_uri_context_free (uri_context);
728 return;
729 }
730
731 /* Any remaining attachments in the list should have MIME parts
732 * only, so we need to save them all to a temporary directory.
733 * We use a directory so the files can retain their basenames.
734 * XXX This could trigger a blocking temp directory cleanup. */
735 template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
736 path = e_mkdtemp (template);
737 g_free (template);
738
739 /* XXX Let's hope errno got set properly. */
740 if (path == NULL) {
741 GSimpleAsyncResult *simple;
742
743 simple = uri_context->simple;
744 g_simple_async_result_set_error (
745 simple, G_FILE_ERROR,
746 g_file_error_from_errno (errno),
747 "%s", g_strerror (errno));
748 g_simple_async_result_complete (simple);
749
750 attachment_store_uri_context_free (uri_context);
751 return;
752 }
753
754 temp_directory = g_file_new_for_path (path);
755
756 for (iter = attachment_list; iter != NULL; iter = iter->next)
757 e_attachment_save_async (
758 E_ATTACHMENT (iter->data),
759 temp_directory, (GAsyncReadyCallback)
760 attachment_store_get_uris_save_cb,
761 uri_context);
762
763 g_object_unref (temp_directory);
764 g_free (path);
765 }
766
767 gchar **
768 e_attachment_store_get_uris_finish (EAttachmentStore *store,
769 GAsyncResult *result,
770 GError **error)
771 {
772 GSimpleAsyncResult *simple;
773 gchar **uris;
774
775 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
776 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
777
778 simple = G_SIMPLE_ASYNC_RESULT (result);
779 uris = g_simple_async_result_get_op_res_gpointer (simple);
780 g_simple_async_result_propagate_error (simple, error);
781
782 return uris;
783 }
784
785 /********************** e_attachment_store_load_async() **********************/
786
787 typedef struct _LoadContext LoadContext;
788
789 struct _LoadContext {
790 GSimpleAsyncResult *simple;
791 GList *attachment_list;
792 GError *error;
793 };
794
795 static LoadContext *
796 attachment_store_load_context_new (EAttachmentStore *store,
797 GList *attachment_list,
798 GAsyncReadyCallback callback,
799 gpointer user_data)
800 {
801 LoadContext *load_context;
802 GSimpleAsyncResult *simple;
803
804 simple = g_simple_async_result_new (
805 G_OBJECT (store), callback, user_data,
806 e_attachment_store_load_async);
807
808 load_context = g_slice_new0 (LoadContext);
809 load_context->simple = simple;
810 load_context->attachment_list = g_list_copy (attachment_list);
811
812 g_list_foreach (
813 load_context->attachment_list,
814 (GFunc) g_object_ref, NULL);
815
816 return load_context;
817 }
818
819 static void
820 attachment_store_load_context_free (LoadContext *load_context)
821 {
822 g_object_unref (load_context->simple);
823
824 /* The attachment list should be empty now. */
825 g_warn_if_fail (load_context->attachment_list == NULL);
826
827 /* So should the error. */
828 g_warn_if_fail (load_context->error == NULL);
829
830 g_slice_free (LoadContext, load_context);
831 }
832
833 static void
834 attachment_store_load_ready_cb (EAttachment *attachment,
835 GAsyncResult *result,
836 LoadContext *load_context)
837 {
838 GSimpleAsyncResult *simple;
839 GError *error = NULL;
840
841 e_attachment_load_finish (attachment, result, &error);
842
843 /* Remove the attachment from the list. */
844 load_context->attachment_list = g_list_remove (
845 load_context->attachment_list, attachment);
846 g_object_unref (attachment);
847
848 if (error != NULL) {
849 /* If this is the first error, cancel the other jobs. */
850 if (load_context->error == NULL) {
851 g_propagate_error (&load_context->error, error);
852 g_list_foreach (
853 load_context->attachment_list,
854 (GFunc) e_attachment_cancel, NULL);
855 error = NULL;
856
857 /* Otherwise, we can only report back one error. So if
858 * this is something other than cancellation, dump it to
859 * the terminal. */
860 } else if (!g_error_matches (
861 error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
862 g_warning ("%s", error->message);
863 }
864
865 if (error != NULL)
866 g_error_free (error);
867
868 /* If there's still jobs running, let them finish. */
869 if (load_context->attachment_list != NULL)
870 return;
871
872 /* Steal the error. */
873 error = load_context->error;
874 load_context->error = NULL;
875
876 simple = load_context->simple;
877
878 if (error == NULL)
879 g_simple_async_result_set_op_res_gboolean (simple, TRUE);
880 else
881 g_simple_async_result_take_error (simple, error);
882
883 g_simple_async_result_complete (simple);
884
885 attachment_store_load_context_free (load_context);
886 }
887
888 void
889 e_attachment_store_load_async (EAttachmentStore *store,
890 GList *attachment_list,
891 GAsyncReadyCallback callback,
892 gpointer user_data)
893 {
894 LoadContext *load_context;
895 GList *iter;
896
897 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
898
899 load_context = attachment_store_load_context_new (
900 store, attachment_list, callback, user_data);
901
902 if (attachment_list == NULL) {
903 GSimpleAsyncResult *simple;
904
905 simple = load_context->simple;
906 g_simple_async_result_set_op_res_gboolean (simple, TRUE);
907 g_simple_async_result_complete (simple);
908
909 attachment_store_load_context_free (load_context);
910 return;
911 }
912
913 for (iter = attachment_list; iter != NULL; iter = iter->next) {
914 EAttachment *attachment = E_ATTACHMENT (iter->data);
915
916 e_attachment_store_add_attachment (store, attachment);
917
918 e_attachment_load_async (
919 attachment, (GAsyncReadyCallback)
920 attachment_store_load_ready_cb,
921 load_context);
922 }
923 }
924
925 gboolean
926 e_attachment_store_load_finish (EAttachmentStore *store,
927 GAsyncResult *result,
928 GError **error)
929 {
930 GSimpleAsyncResult *simple;
931 gboolean success;
932
933 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
934 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
935
936 simple = G_SIMPLE_ASYNC_RESULT (result);
937 success = g_simple_async_result_get_op_res_gboolean (simple);
938 g_simple_async_result_propagate_error (simple, error);
939
940 return success;
941 }
942
943 /********************** e_attachment_store_save_async() **********************/
944
945 typedef struct _SaveContext SaveContext;
946
947 struct _SaveContext {
948 GSimpleAsyncResult *simple;
949 GFile *destination;
950 gchar *filename_prefix;
951 GFile *fresh_directory;
952 GFile *trash_directory;
953 GList *attachment_list;
954 GError *error;
955 gchar **uris;
956 gint index;
957 };
958
959 static SaveContext *
960 attachment_store_save_context_new (EAttachmentStore *store,
961 GFile *destination,
962 const gchar *filename_prefix,
963 GAsyncReadyCallback callback,
964 gpointer user_data)
965 {
966 SaveContext *save_context;
967 GSimpleAsyncResult *simple;
968 GList *attachment_list;
969 guint length;
970 gchar **uris;
971
972 simple = g_simple_async_result_new (
973 G_OBJECT (store), callback, user_data,
974 e_attachment_store_save_async);
975
976 attachment_list = e_attachment_store_get_attachments (store);
977
978 /* Add one for NULL terminator. */
979 length = g_list_length (attachment_list) + 1;
980 uris = g_malloc0 (sizeof (gchar *) * length);
981
982 save_context = g_slice_new0 (SaveContext);
983 save_context->simple = simple;
984 save_context->destination = g_object_ref (destination);
985 save_context->filename_prefix = g_strdup (filename_prefix);
986 save_context->attachment_list = attachment_list;
987 save_context->uris = uris;
988
989 return save_context;
990 }
991
992 static void
993 attachment_store_save_context_free (SaveContext *save_context)
994 {
995 g_object_unref (save_context->simple);
996
997 /* The attachment list should be empty now. */
998 g_warn_if_fail (save_context->attachment_list == NULL);
999
1000 /* So should the error. */
1001 g_warn_if_fail (save_context->error == NULL);
1002
1003 if (save_context->destination) {
1004 g_object_unref (save_context->destination);
1005 save_context->destination = NULL;
1006 }
1007
1008 g_free (save_context->filename_prefix);
1009 save_context->filename_prefix = NULL;
1010
1011 if (save_context->fresh_directory) {
1012 g_object_unref (save_context->fresh_directory);
1013 save_context->fresh_directory = NULL;
1014 }
1015
1016 if (save_context->trash_directory) {
1017 g_object_unref (save_context->trash_directory);
1018 save_context->trash_directory = NULL;
1019 }
1020
1021 g_strfreev (save_context->uris);
1022
1023 g_slice_free (SaveContext, save_context);
1024 }
1025
1026 static void
1027 attachment_store_move_file (SaveContext *save_context,
1028 GFile *source,
1029 GFile *destination,
1030 GError **error)
1031 {
1032 gchar *tmpl;
1033 gchar *path;
1034
1035 g_return_if_fail (save_context != NULL);
1036 g_return_if_fail (source != NULL);
1037 g_return_if_fail (destination != NULL);
1038 g_return_if_fail (error != NULL);
1039
1040 /* Attachments are all saved to a temporary directory.
1041 * Now we need to move the existing destination directory
1042 * out of the way (if it exists). Instead of testing for
1043 * existence we'll just attempt the move and ignore any
1044 * G_IO_ERROR_NOT_FOUND errors. */
1045
1046 /* First, however, we need another temporary directory to
1047 * move the existing destination directory to. Note we're
1048 * not actually creating the directory yet, just picking a
1049 * name for it. The usual raciness with this approach
1050 * applies here (read up on mktemp(3)), but worst case is
1051 * we get a spurious G_IO_ERROR_WOULD_MERGE error and the
1052 * user has to try saving attachments again. */
1053 tmpl = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1054 path = e_mktemp (tmpl);
1055 g_free (tmpl);
1056
1057 save_context->trash_directory = g_file_new_for_path (path);
1058 g_free (path);
1059
1060 /* XXX No asynchronous move operation in GIO? */
1061 g_file_move (
1062 destination,
1063 save_context->trash_directory,
1064 G_FILE_COPY_NONE, NULL, NULL, NULL, error);
1065
1066 if (*error != NULL && !g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
1067 return;
1068
1069 g_clear_error (error);
1070
1071 /* Now we can move the file from the temporary directory
1072 * to the user-specified destination. */
1073 g_file_move (
1074 source,
1075 destination,
1076 G_FILE_COPY_NONE, NULL, NULL, NULL, error);
1077 }
1078
1079 static void
1080 attachment_store_save_cb (EAttachment *attachment,
1081 GAsyncResult *result,
1082 SaveContext *save_context)
1083 {
1084 GSimpleAsyncResult *simple;
1085 GFile *file;
1086 gchar **uris;
1087 GError *error = NULL;
1088
1089 file = e_attachment_save_finish (attachment, result, &error);
1090
1091 /* Remove the attachment from the list. */
1092 save_context->attachment_list = g_list_remove (
1093 save_context->attachment_list, attachment);
1094 g_object_unref (attachment);
1095
1096 if (file != NULL) {
1097 /* Assemble the file's final URI from its basename. */
1098 gchar *basename;
1099 gchar *uri;
1100 GFile *source = NULL, *destination = NULL;
1101
1102 basename = g_file_get_basename (file);
1103 g_object_unref (file);
1104
1105 source = g_file_get_child (save_context->fresh_directory, basename);
1106
1107 if (save_context->filename_prefix && *save_context->filename_prefix) {
1108 gchar *tmp = basename;
1109
1110 basename = g_strconcat (save_context->filename_prefix, basename, NULL);
1111 g_free (tmp);
1112 }
1113
1114 file = save_context->destination;
1115 destination = g_file_get_child (file, basename);
1116 uri = g_file_get_uri (destination);
1117
1118 /* move them file-by-file */
1119 attachment_store_move_file (save_context, source, destination, &error);
1120
1121 if (!error)
1122 save_context->uris[save_context->index++] = uri;
1123
1124 g_object_unref (source);
1125 g_object_unref (destination);
1126 }
1127
1128 if (error != NULL) {
1129 /* If this is the first error, cancel the other jobs. */
1130 if (save_context->error == NULL) {
1131 g_propagate_error (&save_context->error, error);
1132 g_list_foreach (
1133 save_context->attachment_list,
1134 (GFunc) e_attachment_cancel, NULL);
1135 error = NULL;
1136
1137 /* Otherwise, we can only report back one error. So if
1138 * this is something other than cancellation, dump it to
1139 * the terminal. */
1140 } else if (!g_error_matches (
1141 error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1142 g_warning ("%s", error->message);
1143 }
1144
1145 g_clear_error (&error);
1146
1147 /* If there's still jobs running, let them finish. */
1148 if (save_context->attachment_list != NULL)
1149 return;
1150
1151 /* If an error occurred while saving, we're done. */
1152 if (save_context->error != NULL) {
1153
1154 /* Steal the error. */
1155 error = save_context->error;
1156 save_context->error = NULL;
1157
1158 simple = save_context->simple;
1159 g_simple_async_result_take_error (simple, error);
1160 g_simple_async_result_complete (simple);
1161
1162 attachment_store_save_context_free (save_context);
1163 return;
1164 }
1165
1166 if (error != NULL) {
1167 simple = save_context->simple;
1168 g_simple_async_result_take_error (simple, error);
1169 g_simple_async_result_complete (simple);
1170
1171 attachment_store_save_context_free (save_context);
1172 return;
1173 }
1174
1175 /* clean-up left directory */
1176 g_file_delete (save_context->fresh_directory, NULL, NULL);
1177
1178 /* And the URI list. */
1179 uris = save_context->uris;
1180 save_context->uris = NULL;
1181
1182 simple = save_context->simple;
1183 g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1184 g_simple_async_result_complete (simple);
1185
1186 attachment_store_save_context_free (save_context);
1187 }
1188 /*
1189 * @filename_prefix: prefix to use for a file name; can be %NULL for none
1190 **/
1191 void
1192 e_attachment_store_save_async (EAttachmentStore *store,
1193 GFile *destination,
1194 const gchar *filename_prefix,
1195 GAsyncReadyCallback callback,
1196 gpointer user_data)
1197 {
1198 SaveContext *save_context;
1199 GList *attachment_list, *iter;
1200 GFile *temp_directory;
1201 gchar *template;
1202 gchar *path;
1203
1204 g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
1205 g_return_if_fail (G_IS_FILE (destination));
1206
1207 save_context = attachment_store_save_context_new (
1208 store, destination, filename_prefix, callback, user_data);
1209
1210 attachment_list = save_context->attachment_list;
1211
1212 /* Deal with an empty attachment store. The caller will get
1213 * an empty NULL-terminated list as opposed to NULL, to help
1214 * distinguish it from an error. */
1215 if (attachment_list == NULL) {
1216 GSimpleAsyncResult *simple;
1217 gchar **uris;
1218
1219 /* Steal the URI list. */
1220 uris = save_context->uris;
1221 save_context->uris = NULL;
1222
1223 simple = save_context->simple;
1224 g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1225 g_simple_async_result_complete (simple);
1226
1227 attachment_store_save_context_free (save_context);
1228 return;
1229 }
1230
1231 /* Save all attachments to a temporary directory, which we'll
1232 * then move to its proper location. We use a directory so
1233 * files can retain their basenames.
1234 * XXX This could trigger a blocking temp directory cleanup. */
1235 template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1236 path = e_mkdtemp (template);
1237 g_free (template);
1238
1239 /* XXX Let's hope errno got set properly. */
1240 if (path == NULL) {
1241 GSimpleAsyncResult *simple;
1242
1243 simple = save_context->simple;
1244 g_simple_async_result_set_error (
1245 simple, G_FILE_ERROR,
1246 g_file_error_from_errno (errno),
1247 "%s", g_strerror (errno));
1248 g_simple_async_result_complete (simple);
1249
1250 attachment_store_save_context_free (save_context);
1251 return;
1252 }
1253
1254 temp_directory = g_file_new_for_path (path);
1255 save_context->fresh_directory = temp_directory;
1256 g_free (path);
1257
1258 for (iter = attachment_list; iter != NULL; iter = iter->next)
1259 e_attachment_save_async (
1260 E_ATTACHMENT (iter->data),
1261 temp_directory, (GAsyncReadyCallback)
1262 attachment_store_save_cb, save_context);
1263 }
1264
1265 gchar **
1266 e_attachment_store_save_finish (EAttachmentStore *store,
1267 GAsyncResult *result,
1268 GError **error)
1269 {
1270 GSimpleAsyncResult *simple;
1271 gchar **uris;
1272
1273 g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
1274 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
1275
1276 simple = G_SIMPLE_ASYNC_RESULT (result);
1277 uris = g_simple_async_result_get_op_res_gpointer (simple);
1278 g_simple_async_result_propagate_error (simple, error);
1279
1280 return uris;
1281 }