No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | e-mail-folder-utils.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | e-mail-folder-utils.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
1 /*
2 * e-mail-folder-utils.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
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include "e-mail-folder-utils.h"
24
25 #include <glib/gi18n-lib.h>
26
27 #include <libedataserver/libedataserver.h>
28
29 #include <libemail-engine/e-mail-session.h>
30 #include <libemail-engine/mail-tools.h>
31
32 #include "e-mail-utils.h"
33
34 /* X-Mailer header value */
35 #define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT)
36
37 typedef struct _AsyncContext AsyncContext;
38
39 struct _AsyncContext {
40 CamelMimeMessage *message;
41 CamelMessageInfo *info;
42 CamelMimePart *part;
43 GHashTable *hash_table;
44 GPtrArray *ptr_array;
45 GFile *destination;
46 gchar *fwd_subject;
47 gchar *message_uid;
48 };
49
50 static void
51 async_context_free (AsyncContext *context)
52 {
53 if (context->message != NULL)
54 g_object_unref (context->message);
55
56 if (context->info != NULL)
57 camel_message_info_free (context->info);
58
59 if (context->part != NULL)
60 g_object_unref (context->part);
61
62 if (context->hash_table != NULL)
63 g_hash_table_unref (context->hash_table);
64
65 if (context->ptr_array != NULL)
66 g_ptr_array_unref (context->ptr_array);
67
68 if (context->destination != NULL)
69 g_object_unref (context->destination);
70
71 g_free (context->fwd_subject);
72 g_free (context->message_uid);
73
74 g_slice_free (AsyncContext, context);
75 }
76
77 static void
78 mail_folder_append_message_thread (GSimpleAsyncResult *simple,
79 GObject *object,
80 GCancellable *cancellable)
81 {
82 AsyncContext *context;
83 GError *error = NULL;
84
85 context = g_simple_async_result_get_op_res_gpointer (simple);
86
87 e_mail_folder_append_message_sync (
88 CAMEL_FOLDER (object), context->message,
89 context->info, &context->message_uid,
90 cancellable, &error);
91
92 if (error != NULL)
93 g_simple_async_result_take_error (simple, error);
94 }
95
96 gboolean
97 e_mail_folder_append_message_sync (CamelFolder *folder,
98 CamelMimeMessage *message,
99 CamelMessageInfo *info,
100 gchar **appended_uid,
101 GCancellable *cancellable,
102 GError **error)
103 {
104 CamelMedium *medium;
105 gboolean success;
106
107 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
108 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
109
110 medium = CAMEL_MEDIUM (message);
111
112 camel_operation_push_message (
113 cancellable,
114 _("Saving message to folder '%s'"),
115 camel_folder_get_display_name (folder));
116
117 if (camel_medium_get_header (medium, "X-Mailer") == NULL)
118 camel_medium_set_header (medium, "X-Mailer", X_MAILER);
119
120 camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
121
122 success = camel_folder_append_message_sync (
123 folder, message, info, appended_uid, cancellable, error);
124
125 camel_operation_pop_message (cancellable);
126
127 return success;
128 }
129
130 void
131 e_mail_folder_append_message (CamelFolder *folder,
132 CamelMimeMessage *message,
133 CamelMessageInfo *info,
134 gint io_priority,
135 GCancellable *cancellable,
136 GAsyncReadyCallback callback,
137 gpointer user_data)
138 {
139 GSimpleAsyncResult *simple;
140 AsyncContext *context;
141
142 g_return_if_fail (CAMEL_IS_FOLDER (folder));
143 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
144
145 context = g_slice_new0 (AsyncContext);
146 context->message = g_object_ref (message);
147
148 if (info != NULL)
149 context->info = camel_message_info_ref (info);
150
151 simple = g_simple_async_result_new (
152 G_OBJECT (folder), callback, user_data,
153 e_mail_folder_append_message);
154
155 g_simple_async_result_set_check_cancellable (simple, cancellable);
156
157 g_simple_async_result_set_op_res_gpointer (
158 simple, context, (GDestroyNotify) async_context_free);
159
160 g_simple_async_result_run_in_thread (
161 simple, mail_folder_append_message_thread,
162 io_priority, cancellable);
163
164 g_object_unref (simple);
165 }
166
167 gboolean
168 e_mail_folder_append_message_finish (CamelFolder *folder,
169 GAsyncResult *result,
170 gchar **appended_uid,
171 GError **error)
172 {
173 GSimpleAsyncResult *simple;
174 AsyncContext *context;
175
176 g_return_val_if_fail (
177 g_simple_async_result_is_valid (
178 result, G_OBJECT (folder),
179 e_mail_folder_append_message), FALSE);
180
181 simple = G_SIMPLE_ASYNC_RESULT (result);
182 context = g_simple_async_result_get_op_res_gpointer (simple);
183
184 if (appended_uid != NULL) {
185 *appended_uid = context->message_uid;
186 context->message_uid = NULL;
187 }
188
189 /* Assume success unless a GError is set. */
190 return !g_simple_async_result_propagate_error (simple, error);
191 }
192
193 static void
194 mail_folder_expunge_thread (GSimpleAsyncResult *simple,
195 GObject *object,
196 GCancellable *cancellable)
197 {
198 GError *error = NULL;
199
200 e_mail_folder_expunge_sync (
201 CAMEL_FOLDER (object), cancellable, &error);
202
203 if (error != NULL)
204 g_simple_async_result_take_error (simple, error);
205 }
206
207 static gboolean
208 mail_folder_expunge_pop3_stores (CamelFolder *folder,
209 GCancellable *cancellable,
210 GError **error)
211 {
212 GHashTable *expunging_uids;
213 CamelStore *parent_store;
214 CamelService *service;
215 CamelSession *session;
216 ESourceRegistry *registry;
217 GPtrArray *uids;
218 GList *list, *link;
219 const gchar *extension_name;
220 gboolean success = TRUE;
221 guint ii;
222
223 parent_store = camel_folder_get_parent_store (folder);
224
225 service = CAMEL_SERVICE (parent_store);
226 session = camel_service_get_session (service);
227 registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
228
229 uids = camel_folder_get_uids (folder);
230
231 if (uids == NULL)
232 return TRUE;
233
234 expunging_uids = g_hash_table_new_full (
235 (GHashFunc) g_str_hash,
236 (GEqualFunc) g_str_equal,
237 (GDestroyNotify) g_free,
238 (GDestroyNotify) g_free);
239
240 for (ii = 0; ii < uids->len; ii++) {
241 CamelMessageInfo *info;
242 CamelMessageFlags flags = 0;
243 CamelMimeMessage *message;
244 const gchar *pop3_uid;
245 const gchar *source_uid;
246
247 info = camel_folder_get_message_info (
248 folder, uids->pdata[ii]);
249
250 if (info != NULL) {
251 flags = camel_message_info_flags (info);
252 camel_folder_free_message_info (folder, info);
253 }
254
255 /* Only interested in deleted messages. */
256 if ((flags & CAMEL_MESSAGE_DELETED) == 0)
257 continue;
258
259 /* because the UID in the local store doesn't
260 * match with the UID in the pop3 store */
261 message = camel_folder_get_message_sync (
262 folder, uids->pdata[ii], cancellable, NULL);
263
264 if (message == NULL)
265 continue;
266
267 pop3_uid = camel_medium_get_header (
268 CAMEL_MEDIUM (message), "X-Evolution-POP3-UID");
269 source_uid = camel_mime_message_get_source (message);
270
271 if (pop3_uid != NULL)
272 g_hash_table_insert (
273 expunging_uids,
274 g_strstrip (g_strdup (pop3_uid)),
275 g_strstrip (g_strdup (source_uid)));
276
277 g_object_unref (message);
278 }
279
280 camel_folder_free_uids (folder, uids);
281 uids = NULL;
282
283 if (g_hash_table_size (expunging_uids) == 0) {
284 g_hash_table_destroy (expunging_uids);
285 return TRUE;
286 }
287
288 extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
289 list = e_source_registry_list_sources (registry, extension_name);
290
291 for (link = list; link != NULL; link = g_list_next (link)) {
292 ESource *source = E_SOURCE (link->data);
293 ESourceBackend *extension;
294 CamelFolder *folder;
295 CamelService *service;
296 CamelSettings *settings;
297 const gchar *backend_name;
298 const gchar *service_uid;
299 const gchar *source_uid;
300 gboolean any_found = FALSE;
301 gboolean delete_expunged = FALSE;
302 gboolean keep_on_server = FALSE;
303
304 source_uid = e_source_get_uid (source);
305
306 extension = e_source_get_extension (source, extension_name);
307 backend_name = e_source_backend_get_backend_name (extension);
308
309 if (!em_utils_is_source_enabled_with_parents (registry, source) ||
310 g_strcmp0 (backend_name, "pop") != 0)
311 continue;
312
313 service = camel_session_ref_service (
314 CAMEL_SESSION (session), source_uid);
315
316 service_uid = camel_service_get_uid (service);
317 settings = camel_service_ref_settings (service);
318
319 g_object_get (
320 settings,
321 "delete-expunged", &delete_expunged,
322 "keep-on-server", &keep_on_server,
323 NULL);
324
325 g_object_unref (settings);
326
327 if (!keep_on_server || !delete_expunged) {
328 g_object_unref (service);
329 continue;
330 }
331
332 folder = camel_store_get_inbox_folder_sync (
333 CAMEL_STORE (service), cancellable, error);
334
335 /* Abort the loop on error. */
336 if (folder == NULL) {
337 g_object_unref (service);
338 success = FALSE;
339 break;
340 }
341
342 uids = camel_folder_get_uids (folder);
343
344 if (uids == NULL) {
345 g_object_unref (service);
346 g_object_unref (folder);
347 continue;
348 }
349
350 for (ii = 0; ii < uids->len; ii++) {
351 const gchar *source_uid;
352
353 source_uid = g_hash_table_lookup (
354 expunging_uids, uids->pdata[ii]);
355 if (g_strcmp0 (source_uid, service_uid) == 0) {
356 any_found = TRUE;
357 camel_folder_delete_message (
358 folder, uids->pdata[ii]);
359 }
360 }
361
362 camel_folder_free_uids (folder, uids);
363
364 if (any_found)
365 success = camel_folder_synchronize_sync (
366 folder, TRUE, cancellable, error);
367
368 g_object_unref (folder);
369 g_object_unref (service);
370
371 /* Abort the loop on error. */
372 if (!success)
373 break;
374 }
375
376 g_list_free_full (list, (GDestroyNotify) g_object_unref);
377
378 g_hash_table_destroy (expunging_uids);
379
380 return success;
381 }
382
383 gboolean
384 e_mail_folder_expunge_sync (CamelFolder *folder,
385 GCancellable *cancellable,
386 GError **error)
387 {
388 CamelFolder *local_inbox;
389 CamelStore *parent_store;
390 CamelService *service;
391 CamelSession *session;
392 gboolean is_local_inbox;
393 gboolean is_local_trash;
394 gboolean store_is_local;
395 gboolean success = TRUE;
396 const gchar *uid;
397
398 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
399
400 parent_store = camel_folder_get_parent_store (folder);
401
402 service = CAMEL_SERVICE (parent_store);
403 session = camel_service_get_session (service);
404
405 uid = camel_service_get_uid (service);
406 store_is_local = (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
407
408 local_inbox = e_mail_session_get_local_folder (
409 E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_INBOX);
410 is_local_inbox = (folder == local_inbox);
411 is_local_trash = FALSE;
412
413 if (store_is_local && !is_local_inbox) {
414 CamelFolder *local_trash;
415
416 local_trash = camel_store_get_trash_folder_sync (
417 parent_store, cancellable, error);
418
419 if (local_trash != NULL) {
420 is_local_trash = (folder == local_trash);
421 g_object_unref (local_trash);
422 } else {
423 return FALSE;
424 }
425 }
426
427 /* Expunge all POP3 accounts when expunging
428 * the local Inbox or Trash folder. */
429 if (is_local_inbox || is_local_trash)
430 success = mail_folder_expunge_pop3_stores (
431 folder, cancellable, error);
432
433 if (success)
434 success = camel_folder_expunge_sync (
435 folder, cancellable, error);
436
437 return success;
438 }
439
440 void
441 e_mail_folder_expunge (CamelFolder *folder,
442 gint io_priority,
443 GCancellable *cancellable,
444 GAsyncReadyCallback callback,
445 gpointer user_data)
446 {
447 GSimpleAsyncResult *simple;
448
449 g_return_if_fail (CAMEL_IS_FOLDER (folder));
450
451 simple = g_simple_async_result_new (
452 G_OBJECT (folder), callback,
453 user_data, e_mail_folder_expunge);
454
455 g_simple_async_result_set_check_cancellable (simple, cancellable);
456
457 g_simple_async_result_run_in_thread (
458 simple, mail_folder_expunge_thread,
459 io_priority, cancellable);
460
461 g_object_unref (simple);
462 }
463
464 gboolean
465 e_mail_folder_expunge_finish (CamelFolder *folder,
466 GAsyncResult *result,
467 GError **error)
468 {
469 GSimpleAsyncResult *simple;
470
471 g_return_val_if_fail (
472 g_simple_async_result_is_valid (
473 result, G_OBJECT (folder),
474 e_mail_folder_expunge), FALSE);
475
476 simple = G_SIMPLE_ASYNC_RESULT (result);
477
478 /* Assume success unless a GError is set. */
479 return !g_simple_async_result_propagate_error (simple, error);
480 }
481
482 static void
483 mail_folder_build_attachment_thread (GSimpleAsyncResult *simple,
484 GObject *object,
485 GCancellable *cancellable)
486 {
487 AsyncContext *context;
488 GError *error = NULL;
489
490 context = g_simple_async_result_get_op_res_gpointer (simple);
491
492 context->part = e_mail_folder_build_attachment_sync (
493 CAMEL_FOLDER (object), context->ptr_array,
494 &context->fwd_subject, cancellable, &error);
495
496 if (error != NULL)
497 g_simple_async_result_take_error (simple, error);
498 }
499
500 CamelMimePart *
501 e_mail_folder_build_attachment_sync (CamelFolder *folder,
502 GPtrArray *message_uids,
503 gchar **fwd_subject,
504 GCancellable *cancellable,
505 GError **error)
506 {
507 GHashTable *hash_table;
508 CamelMimeMessage *message;
509 CamelMimePart *part;
510 const gchar *uid;
511
512 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
513 g_return_val_if_fail (message_uids != NULL, NULL);
514
515 /* Need at least one message UID to make an attachment. */
516 g_return_val_if_fail (message_uids->len > 0, NULL);
517
518 hash_table = e_mail_folder_get_multiple_messages_sync (
519 folder, message_uids, cancellable, error);
520
521 if (hash_table == NULL)
522 return NULL;
523
524 /* Create the forward subject from the first message. */
525
526 uid = g_ptr_array_index (message_uids, 0);
527 g_return_val_if_fail (uid != NULL, NULL);
528
529 message = g_hash_table_lookup (hash_table, uid);
530 g_return_val_if_fail (message != NULL, NULL);
531
532 if (fwd_subject != NULL)
533 *fwd_subject = mail_tool_generate_forward_subject (message);
534
535 if (message_uids->len == 1) {
536 part = mail_tool_make_message_attachment (message);
537
538 } else {
539 CamelMultipart *multipart;
540 guint ii;
541
542 multipart = camel_multipart_new ();
543 camel_data_wrapper_set_mime_type (
544 CAMEL_DATA_WRAPPER (multipart), "multipart/digest");
545 camel_multipart_set_boundary (multipart, NULL);
546
547 for (ii = 0; ii < message_uids->len; ii++) {
548 uid = g_ptr_array_index (message_uids, ii);
549 g_return_val_if_fail (uid != NULL, NULL);
550
551 message = g_hash_table_lookup (hash_table, uid);
552 g_return_val_if_fail (message != NULL, NULL);
553
554 part = mail_tool_make_message_attachment (message);
555 camel_multipart_add_part (multipart, part);
556 g_object_unref (part);
557 }
558
559 part = camel_mime_part_new ();
560
561 camel_medium_set_content (
562 CAMEL_MEDIUM (part),
563 CAMEL_DATA_WRAPPER (multipart));
564
565 camel_mime_part_set_description (
566 part, _("Forwarded messages"));
567
568 g_object_unref (multipart);
569 }
570
571 g_hash_table_unref (hash_table);
572
573 return part;
574 }
575
576 void
577 e_mail_folder_build_attachment (CamelFolder *folder,
578 GPtrArray *message_uids,
579 gint io_priority,
580 GCancellable *cancellable,
581 GAsyncReadyCallback callback,
582 gpointer user_data)
583 {
584 GSimpleAsyncResult *simple;
585 AsyncContext *context;
586
587 g_return_if_fail (CAMEL_IS_FOLDER (folder));
588 g_return_if_fail (message_uids != NULL);
589
590 /* Need at least one message UID to make an attachment. */
591 g_return_if_fail (message_uids->len > 0);
592
593 context = g_slice_new0 (AsyncContext);
594 context->ptr_array = g_ptr_array_ref (message_uids);
595
596 simple = g_simple_async_result_new (
597 G_OBJECT (folder), callback, user_data,
598 e_mail_folder_build_attachment);
599
600 g_simple_async_result_set_check_cancellable (simple, cancellable);
601
602 g_simple_async_result_set_op_res_gpointer (
603 simple, context, (GDestroyNotify) async_context_free);
604
605 g_simple_async_result_run_in_thread (
606 simple, mail_folder_build_attachment_thread,
607 io_priority, cancellable);
608
609 g_object_unref (simple);
610 }
611
612 CamelMimePart *
613 e_mail_folder_build_attachment_finish (CamelFolder *folder,
614 GAsyncResult *result,
615 gchar **fwd_subject,
616 GError **error)
617 {
618 GSimpleAsyncResult *simple;
619 AsyncContext *context;
620
621 g_return_val_if_fail (
622 g_simple_async_result_is_valid (
623 result, G_OBJECT (folder),
624 e_mail_folder_build_attachment), NULL);
625
626 simple = G_SIMPLE_ASYNC_RESULT (result);
627 context = g_simple_async_result_get_op_res_gpointer (simple);
628
629 if (g_simple_async_result_propagate_error (simple, error))
630 return NULL;
631
632 if (fwd_subject != NULL) {
633 *fwd_subject = context->fwd_subject;
634 context->fwd_subject = NULL;
635 }
636
637 g_return_val_if_fail (CAMEL_IS_MIME_PART (context->part), NULL);
638
639 return g_object_ref (context->part);
640 }
641
642 static void
643 mail_folder_find_duplicate_messages_thread (GSimpleAsyncResult *simple,
644 GObject *object,
645 GCancellable *cancellable)
646 {
647 AsyncContext *context;
648 GError *error = NULL;
649
650 context = g_simple_async_result_get_op_res_gpointer (simple);
651
652 context->hash_table = e_mail_folder_find_duplicate_messages_sync (
653 CAMEL_FOLDER (object), context->ptr_array,
654 cancellable, &error);
655
656 if (error != NULL)
657 g_simple_async_result_take_error (simple, error);
658 }
659
660 static GHashTable *
661 emfu_get_messages_hash_sync (CamelFolder *folder,
662 GPtrArray *message_uids,
663 GCancellable *cancellable,
664 GError **error)
665 {
666 GHashTable *hash_table;
667 CamelMimeMessage *message;
668 guint ii;
669
670 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
671 g_return_val_if_fail (message_uids != NULL, NULL);
672
673 camel_operation_push_message (
674 cancellable,
675 ngettext (
676 "Retrieving %d message",
677 "Retrieving %d messages",
678 message_uids->len),
679 message_uids->len);
680
681 hash_table = g_hash_table_new_full (
682 (GHashFunc) g_str_hash,
683 (GEqualFunc) g_str_equal,
684 (GDestroyNotify) g_free,
685 (GDestroyNotify) g_free);
686
687 /* This is an all or nothing operation. Destroy the
688 * hash table if we fail to retrieve any message. */
689
690 for (ii = 0; ii < message_uids->len; ii++) {
691 const gchar *uid;
692 gint percent;
693
694 uid = g_ptr_array_index (message_uids, ii);
695 percent = ((ii + 1) * 100) / message_uids->len;
696
697 message = camel_folder_get_message_sync (
698 folder, uid, cancellable, error);
699
700 camel_operation_progress (cancellable, percent);
701
702 if (CAMEL_IS_MIME_MESSAGE (message)) {
703 CamelDataWrapper *content;
704 gchar *digest = NULL;
705
706 /* Generate a digest string from the message's content. */
707 content = camel_medium_get_content (CAMEL_MEDIUM (message));
708
709 if (content != NULL) {
710 CamelStream *stream;
711 GByteArray *buffer;
712 gssize n_bytes;
713
714 stream = camel_stream_mem_new ();
715
716 n_bytes = camel_data_wrapper_decode_to_stream_sync (
717 content, stream, cancellable, error);
718
719 if (n_bytes >= 0) {
720 /* The CamelStreamMem owns the buffer. */
721 buffer = camel_stream_mem_get_byte_array (
722 CAMEL_STREAM_MEM (stream));
723 g_return_val_if_fail (buffer != NULL, NULL);
724
725 digest = g_compute_checksum_for_data (
726 G_CHECKSUM_SHA256, buffer->data, buffer->len);
727 }
728
729 g_object_unref (stream);
730 }
731
732 g_hash_table_insert (
733 hash_table, g_strdup (uid), digest);
734 g_object_unref (message);
735 } else {
736 g_hash_table_destroy (hash_table);
737 hash_table = NULL;
738 break;
739 }
740 }
741
742 camel_operation_pop_message (cancellable);
743
744 return hash_table;
745 }
746
747 GHashTable *
748 e_mail_folder_find_duplicate_messages_sync (CamelFolder *folder,
749 GPtrArray *message_uids,
750 GCancellable *cancellable,
751 GError **error)
752 {
753 GQueue trash = G_QUEUE_INIT;
754 GHashTable *hash_table;
755 GHashTable *unique_ids;
756 GHashTableIter iter;
757 gpointer key, value;
758
759 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
760 g_return_val_if_fail (message_uids != NULL, NULL);
761
762 /* hash_table = { MessageUID : digest-as-string } */
763 hash_table = emfu_get_messages_hash_sync (
764 folder, message_uids, cancellable, error);
765
766 if (hash_table == NULL)
767 return NULL;
768
769 camel_operation_push_message (
770 cancellable, _("Scanning messages for duplicates"));
771
772 unique_ids = g_hash_table_new_full (
773 (GHashFunc) g_int64_hash,
774 (GEqualFunc) g_int64_equal,
775 (GDestroyNotify) g_free,
776 (GDestroyNotify) g_free);
777
778 g_hash_table_iter_init (&iter, hash_table);
779
780 while (g_hash_table_iter_next (&iter, &key, &value)) {
781 const CamelSummaryMessageID *message_id;
782 CamelMessageFlags flags;
783 CamelMessageInfo *info;
784 gboolean duplicate;
785 const gchar *digest;
786
787 info = camel_folder_get_message_info (folder, key);
788 message_id = camel_message_info_message_id (info);
789 flags = camel_message_info_flags (info);
790
791 /* Skip messages marked for deletion. */
792 if (flags & CAMEL_MESSAGE_DELETED) {
793 g_queue_push_tail (&trash, key);
794 camel_message_info_free (info);
795 continue;
796 }
797
798 digest = value;
799
800 if (digest == NULL) {
801 g_queue_push_tail (&trash, key);
802 camel_message_info_free (info);
803 continue;
804 }
805
806 /* Determine if the message a duplicate. */
807
808 value = g_hash_table_lookup (unique_ids, &message_id->id.id);
809 duplicate = (value != NULL) && g_str_equal (digest, value);
810
811 if (!duplicate) {
812 gint64 *v_int64;
813
814 /* XXX Might be better to create a GArray
815 * of 64-bit integers and have the hash
816 * table keys point to array elements. */
817 v_int64 = g_new0 (gint64, 1);
818 *v_int64 = (gint64) message_id->id.id;
819
820 g_hash_table_insert (unique_ids, v_int64, g_strdup (digest));
821 g_queue_push_tail (&trash, key);
822 }
823
824 camel_message_info_free (info);
825 }
826
827 /* Delete all non-duplicate messages from the hash table. */
828 while ((key = g_queue_pop_head (&trash)) != NULL)
829 g_hash_table_remove (hash_table, key);
830
831 camel_operation_pop_message (cancellable);
832
833 g_hash_table_destroy (unique_ids);
834
835 return hash_table;
836 }
837
838 void
839 e_mail_folder_find_duplicate_messages (CamelFolder *folder,
840 GPtrArray *message_uids,
841 gint io_priority,
842 GCancellable *cancellable,
843 GAsyncReadyCallback callback,
844 gpointer user_data)
845 {
846 GSimpleAsyncResult *simple;
847 AsyncContext *context;
848
849 g_return_if_fail (CAMEL_IS_FOLDER (folder));
850 g_return_if_fail (message_uids != NULL);
851
852 context = g_slice_new0 (AsyncContext);
853 context->ptr_array = g_ptr_array_ref (message_uids);
854
855 simple = g_simple_async_result_new (
856 G_OBJECT (folder), callback, user_data,
857 e_mail_folder_find_duplicate_messages);
858
859 g_simple_async_result_set_check_cancellable (simple, cancellable);
860
861 g_simple_async_result_set_op_res_gpointer (
862 simple, context, (GDestroyNotify) async_context_free);
863
864 g_simple_async_result_run_in_thread (
865 simple, mail_folder_find_duplicate_messages_thread,
866 io_priority, cancellable);
867
868 g_object_unref (simple);
869 }
870
871 GHashTable *
872 e_mail_folder_find_duplicate_messages_finish (CamelFolder *folder,
873 GAsyncResult *result,
874 GError **error)
875 {
876 GSimpleAsyncResult *simple;
877 AsyncContext *context;
878
879 g_return_val_if_fail (
880 g_simple_async_result_is_valid (
881 result, G_OBJECT (folder),
882 e_mail_folder_find_duplicate_messages), NULL);
883
884 simple = G_SIMPLE_ASYNC_RESULT (result);
885 context = g_simple_async_result_get_op_res_gpointer (simple);
886
887 if (g_simple_async_result_propagate_error (simple, error))
888 return NULL;
889
890 return g_hash_table_ref (context->hash_table);
891 }
892
893 static void
894 mail_folder_get_multiple_messages_thread (GSimpleAsyncResult *simple,
895 GObject *object,
896 GCancellable *cancellable)
897 {
898 AsyncContext *context;
899 GError *error = NULL;
900
901 context = g_simple_async_result_get_op_res_gpointer (simple);
902
903 context->hash_table = e_mail_folder_get_multiple_messages_sync (
904 CAMEL_FOLDER (object), context->ptr_array,
905 cancellable, &error);
906
907 if (error != NULL)
908 g_simple_async_result_take_error (simple, error);
909 }
910
911 GHashTable *
912 e_mail_folder_get_multiple_messages_sync (CamelFolder *folder,
913 GPtrArray *message_uids,
914 GCancellable *cancellable,
915 GError **error)
916 {
917 GHashTable *hash_table;
918 CamelMimeMessage *message;
919 guint ii;
920
921 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
922 g_return_val_if_fail (message_uids != NULL, NULL);
923
924 camel_operation_push_message (
925 cancellable,
926 ngettext (
927 "Retrieving %d message",
928 "Retrieving %d messages",
929 message_uids->len),
930 message_uids->len);
931
932 hash_table = g_hash_table_new_full (
933 (GHashFunc) g_str_hash,
934 (GEqualFunc) g_str_equal,
935 (GDestroyNotify) g_free,
936 (GDestroyNotify) g_object_unref);
937
938 /* This is an all or nothing operation. Destroy the
939 * hash table if we fail to retrieve any message. */
940
941 for (ii = 0; ii < message_uids->len; ii++) {
942 const gchar *uid;
943 gint percent;
944
945 uid = g_ptr_array_index (message_uids, ii);
946 percent = ((ii + 1) * 100) / message_uids->len;
947
948 message = camel_folder_get_message_sync (
949 folder, uid, cancellable, error);
950
951 camel_operation_progress (cancellable, percent);
952
953 if (CAMEL_IS_MIME_MESSAGE (message)) {
954 g_hash_table_insert (
955 hash_table, g_strdup (uid), message);
956 } else {
957 g_hash_table_destroy (hash_table);
958 hash_table = NULL;
959 break;
960 }
961 }
962
963 camel_operation_pop_message (cancellable);
964
965 return hash_table;
966 }
967
968 void
969 e_mail_folder_get_multiple_messages (CamelFolder *folder,
970 GPtrArray *message_uids,
971 gint io_priority,
972 GCancellable *cancellable,
973 GAsyncReadyCallback callback,
974 gpointer user_data)
975 {
976 GSimpleAsyncResult *simple;
977 AsyncContext *context;
978
979 g_return_if_fail (CAMEL_IS_FOLDER (folder));
980 g_return_if_fail (message_uids != NULL);
981
982 context = g_slice_new0 (AsyncContext);
983 context->ptr_array = g_ptr_array_ref (message_uids);
984
985 simple = g_simple_async_result_new (
986 G_OBJECT (folder), callback, user_data,
987 e_mail_folder_get_multiple_messages);
988
989 g_simple_async_result_set_check_cancellable (simple, cancellable);
990
991 g_simple_async_result_set_op_res_gpointer (
992 simple, context, (GDestroyNotify) async_context_free);
993
994 g_simple_async_result_run_in_thread (
995 simple, mail_folder_get_multiple_messages_thread,
996 io_priority, cancellable);
997
998 g_object_unref (simple);
999 }
1000
1001 GHashTable *
1002 e_mail_folder_get_multiple_messages_finish (CamelFolder *folder,
1003 GAsyncResult *result,
1004 GError **error)
1005 {
1006 GSimpleAsyncResult *simple;
1007 AsyncContext *context;
1008
1009 g_return_val_if_fail (
1010 g_simple_async_result_is_valid (
1011 result, G_OBJECT (folder),
1012 e_mail_folder_get_multiple_messages), NULL);
1013
1014 simple = G_SIMPLE_ASYNC_RESULT (result);
1015 context = g_simple_async_result_get_op_res_gpointer (simple);
1016
1017 if (g_simple_async_result_propagate_error (simple, error))
1018 return NULL;
1019
1020 return g_hash_table_ref (context->hash_table);
1021 }
1022
1023 static void
1024 mail_folder_remove_thread (GSimpleAsyncResult *simple,
1025 GObject *object,
1026 GCancellable *cancellable)
1027 {
1028 GError *error = NULL;
1029
1030 e_mail_folder_remove_sync (
1031 CAMEL_FOLDER (object), cancellable, &error);
1032
1033 if (error != NULL)
1034 g_simple_async_result_take_error (simple, error);
1035 }
1036
1037 static gboolean
1038 mail_folder_remove_recursive (CamelStore *store,
1039 CamelFolderInfo *folder_info,
1040 GCancellable *cancellable,
1041 GError **error)
1042 {
1043 gboolean success = TRUE;
1044
1045 while (folder_info != NULL) {
1046 CamelFolder *folder;
1047
1048 if (folder_info->child != NULL) {
1049 success = mail_folder_remove_recursive (
1050 store, folder_info->child, cancellable, error);
1051 if (!success)
1052 break;
1053 }
1054
1055 folder = camel_store_get_folder_sync (
1056 store, folder_info->full_name, 0, cancellable, error);
1057 if (folder == NULL) {
1058 success = FALSE;
1059 break;
1060 }
1061
1062 if (!CAMEL_IS_VEE_FOLDER (folder)) {
1063 GPtrArray *uids;
1064 guint ii;
1065
1066 /* Delete every message in this folder,
1067 * then expunge it. */
1068
1069 camel_folder_freeze (folder);
1070
1071 uids = camel_folder_get_uids (folder);
1072
1073 for (ii = 0; ii < uids->len; ii++)
1074 camel_folder_delete_message (
1075 folder, uids->pdata[ii]);
1076
1077 camel_folder_free_uids (folder, uids);
1078
1079 success = camel_folder_synchronize_sync (
1080 folder, TRUE, cancellable, error);
1081
1082 camel_folder_thaw (folder);
1083 }
1084
1085 g_object_unref (folder);
1086
1087 if (!success)
1088 break;
1089
1090 /* If the store supports subscriptions,
1091 * then unsubscribe from this folder. */
1092 if (CAMEL_IS_SUBSCRIBABLE (store)) {
1093 success = camel_subscribable_unsubscribe_folder_sync (
1094 CAMEL_SUBSCRIBABLE (store),
1095 folder_info->full_name,
1096 cancellable, error);
1097 if (!success)
1098 break;
1099 }
1100
1101 success = camel_store_delete_folder_sync (
1102 store, folder_info->full_name, cancellable, error);
1103 if (!success)
1104 break;
1105
1106 folder_info = folder_info->next;
1107 }
1108
1109 return success;
1110 }
1111
1112 static void
1113 follow_cancel_cb (GCancellable *cancellable,
1114 GCancellable *transparent_cancellable)
1115 {
1116 g_cancellable_cancel (transparent_cancellable);
1117 }
1118
1119 gboolean
1120 e_mail_folder_remove_sync (CamelFolder *folder,
1121 GCancellable *cancellable,
1122 GError **error)
1123 {
1124 CamelFolderInfo *folder_info;
1125 CamelFolderInfo *to_remove;
1126 CamelFolderInfo *next = NULL;
1127 CamelStore *parent_store;
1128 const gchar *display_name;
1129 const gchar *full_name;
1130 const gchar *message;
1131 gboolean success = TRUE;
1132 GCancellable *transparent_cancellable = NULL;
1133 gulong cbid = 0;
1134
1135 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1136
1137 full_name = camel_folder_get_full_name (folder);
1138 parent_store = camel_folder_get_parent_store (folder);
1139
1140 folder_info = camel_store_get_folder_info_sync (
1141 parent_store, full_name,
1142 CAMEL_STORE_FOLDER_INFO_FAST |
1143 CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1144 CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
1145 cancellable, error);
1146
1147 if (folder_info == NULL)
1148 return FALSE;
1149
1150 to_remove = folder_info;
1151
1152 /* For cases when the top-level folder_info contains siblings,
1153 * such as when full_name contains a wildcard letter, compare
1154 * the folder name against folder_info->full_name to avoid
1155 * removing more folders than requested. */
1156 if (folder_info->next != NULL) {
1157 while (to_remove != NULL) {
1158 if (g_strcmp0 (to_remove->full_name, full_name) == 0)
1159 break;
1160 to_remove = to_remove->next;
1161 }
1162
1163 /* XXX Should we set a GError and return FALSE here? */
1164 if (to_remove == NULL) {
1165 g_warning (
1166 "%s: Failed to find folder '%s'",
1167 G_STRFUNC, full_name);
1168 camel_store_free_folder_info (
1169 parent_store, folder_info);
1170 return TRUE;
1171 }
1172
1173 /* Prevent iterating over siblings. */
1174 next = to_remove->next;
1175 to_remove->next = NULL;
1176 }
1177
1178 message = _("Removing folder '%s'");
1179 display_name = camel_folder_get_display_name (folder);
1180 camel_operation_push_message (cancellable, message, display_name);
1181
1182 if (cancellable) {
1183 transparent_cancellable = g_cancellable_new ();
1184 cbid = g_cancellable_connect (
1185 cancellable, G_CALLBACK (follow_cancel_cb),
1186 transparent_cancellable, NULL);
1187 }
1188
1189 success = mail_folder_remove_recursive (
1190 parent_store, to_remove, transparent_cancellable, error);
1191
1192 if (transparent_cancellable) {
1193 g_cancellable_disconnect (cancellable, cbid);
1194 g_object_unref (transparent_cancellable);
1195 }
1196
1197 camel_operation_pop_message (cancellable);
1198
1199 /* Restore the folder_info tree to its original
1200 * state so we don't leak folder_info nodes. */
1201 to_remove->next = next;
1202
1203 camel_store_free_folder_info (parent_store, folder_info);
1204
1205 return success;
1206 }
1207
1208 void
1209 e_mail_folder_remove (CamelFolder *folder,
1210 gint io_priority,
1211 GCancellable *cancellable,
1212 GAsyncReadyCallback callback,
1213 gpointer user_data)
1214 {
1215 GSimpleAsyncResult *simple;
1216
1217 g_return_if_fail (CAMEL_IS_FOLDER (folder));
1218
1219 simple = g_simple_async_result_new (
1220 G_OBJECT (folder), callback,
1221 user_data, e_mail_folder_remove);
1222
1223 g_simple_async_result_set_check_cancellable (simple, cancellable);
1224
1225 g_simple_async_result_run_in_thread (
1226 simple, mail_folder_remove_thread,
1227 io_priority, cancellable);
1228
1229 g_object_unref (simple);
1230 }
1231
1232 gboolean
1233 e_mail_folder_remove_finish (CamelFolder *folder,
1234 GAsyncResult *result,
1235 GError **error)
1236 {
1237 GSimpleAsyncResult *simple;
1238
1239 g_return_val_if_fail (
1240 g_simple_async_result_is_valid (
1241 result, G_OBJECT (folder),
1242 e_mail_folder_remove), FALSE);
1243
1244 simple = G_SIMPLE_ASYNC_RESULT (result);
1245
1246 /* Assume success unless a GError is set. */
1247 return !g_simple_async_result_propagate_error (simple, error);
1248 }
1249
1250 static void
1251 mail_folder_remove_attachments_thread (GSimpleAsyncResult *simple,
1252 GObject *object,
1253 GCancellable *cancellable)
1254 {
1255 AsyncContext *context;
1256 GError *error = NULL;
1257
1258 context = g_simple_async_result_get_op_res_gpointer (simple);
1259
1260 e_mail_folder_remove_attachments_sync (
1261 CAMEL_FOLDER (object), context->ptr_array,
1262 cancellable, &error);
1263
1264 if (error != NULL)
1265 g_simple_async_result_take_error (simple, error);
1266 }
1267
1268 /* Helper for e_mail_folder_remove_attachments_sync() */
1269 static gboolean
1270 mail_folder_strip_message (CamelFolder *folder,
1271 CamelMimeMessage *message,
1272 const gchar *message_uid,
1273 GCancellable *cancellable,
1274 GError **error)
1275 {
1276 CamelDataWrapper *content;
1277 CamelMultipart *multipart;
1278 gboolean modified = FALSE;
1279 gboolean success = TRUE;
1280 guint ii, n_parts;
1281
1282 content = camel_medium_get_content (CAMEL_MEDIUM (message));
1283
1284 if (!CAMEL_IS_MULTIPART (content))
1285 return TRUE;
1286
1287 multipart = CAMEL_MULTIPART (content);
1288 n_parts = camel_multipart_get_number (multipart);
1289
1290 /* Replace MIME parts with "attachment" or "inline" dispositions
1291 * with a small "text/plain" part saying the file was removed. */
1292 for (ii = 0; ii < n_parts; ii++) {
1293 CamelMimePart *mime_part;
1294 const gchar *disposition;
1295 gboolean is_attachment;
1296
1297 mime_part = camel_multipart_get_part (multipart, ii);
1298 disposition = camel_mime_part_get_disposition (mime_part);
1299
1300 is_attachment =
1301 (g_strcmp0 (disposition, "attachment") == 0) ||
1302 (g_strcmp0 (disposition, "inline") == 0);
1303
1304 if (is_attachment) {
1305 const gchar *filename;
1306 const gchar *content_type;
1307 gchar *content;
1308
1309 disposition = "inline";
1310 content_type = "text/plain";
1311 filename = camel_mime_part_get_filename (mime_part);
1312
1313 if (filename != NULL && *filename != '\0')
1314 content = g_strdup_printf (
1315 _("File \"%s\" has been removed."),
1316 filename);
1317 else
1318 content = g_strdup (
1319 _("File has been removed."));
1320
1321 camel_mime_part_set_content (
1322 mime_part, content,
1323 strlen (content), content_type);
1324 camel_mime_part_set_content_type (
1325 mime_part, content_type);
1326 camel_mime_part_set_disposition (
1327 mime_part, disposition);
1328
1329 modified = TRUE;
1330 }
1331 }
1332
1333 /* Append the modified message with removed attachments to
1334 * the folder and mark the original message for deletion. */
1335 if (modified) {
1336 CamelMessageInfo *orig_info;
1337 CamelMessageInfo *copy_info;
1338 CamelMessageFlags flags;
1339
1340 orig_info =
1341 camel_folder_get_message_info (folder, message_uid);
1342 copy_info =
1343 camel_message_info_new_from_header (
1344 NULL, CAMEL_MIME_PART (message)->headers);
1345
1346 flags = camel_folder_get_message_flags (folder, message_uid);
1347 camel_message_info_set_flags (copy_info, flags, flags);
1348
1349 success = camel_folder_append_message_sync (
1350 folder, message, copy_info, NULL, cancellable, error);
1351 if (success)
1352 camel_message_info_set_flags (
1353 orig_info,
1354 CAMEL_MESSAGE_DELETED,
1355 CAMEL_MESSAGE_DELETED);
1356
1357 camel_folder_free_message_info (folder, orig_info);
1358 camel_message_info_free (copy_info);
1359 }
1360
1361 return success;
1362 }
1363
1364 gboolean
1365 e_mail_folder_remove_attachments_sync (CamelFolder *folder,
1366 GPtrArray *message_uids,
1367 GCancellable *cancellable,
1368 GError **error)
1369 {
1370 gboolean success = TRUE;
1371 guint ii;
1372
1373 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1374 g_return_val_if_fail (message_uids != NULL, FALSE);
1375
1376 camel_folder_freeze (folder);
1377
1378 camel_operation_push_message (cancellable, _("Removing attachments"));
1379
1380 for (ii = 0; success && ii < message_uids->len; ii++) {
1381 CamelMimeMessage *message;
1382 const gchar *uid;
1383 gint percent;
1384
1385 uid = g_ptr_array_index (message_uids, ii);
1386
1387 message = camel_folder_get_message_sync (
1388 folder, uid, cancellable, error);
1389
1390 if (message == NULL) {
1391 success = FALSE;
1392 break;
1393 }
1394
1395 success = mail_folder_strip_message (
1396 folder, message, uid, cancellable, error);
1397
1398 percent = ((ii + 1) * 100) / message_uids->len;
1399 camel_operation_progress (cancellable, percent);
1400
1401 g_object_unref (message);
1402 }
1403
1404 camel_operation_pop_message (cancellable);
1405
1406 if (success)
1407 camel_folder_synchronize_sync (
1408 folder, FALSE, cancellable, error);
1409
1410 camel_folder_thaw (folder);
1411
1412 return success;
1413 }
1414
1415 void
1416 e_mail_folder_remove_attachments (CamelFolder *folder,
1417 GPtrArray *message_uids,
1418 gint io_priority,
1419 GCancellable *cancellable,
1420 GAsyncReadyCallback callback,
1421 gpointer user_data)
1422 {
1423 GSimpleAsyncResult *simple;
1424 AsyncContext *context;
1425
1426 g_return_if_fail (CAMEL_IS_FOLDER (folder));
1427 g_return_if_fail (message_uids != NULL);
1428
1429 context = g_slice_new0 (AsyncContext);
1430 context->ptr_array = g_ptr_array_ref (message_uids);
1431
1432 simple = g_simple_async_result_new (
1433 G_OBJECT (folder), callback, user_data,
1434 e_mail_folder_remove_attachments);
1435
1436 g_simple_async_result_set_check_cancellable (simple, cancellable);
1437
1438 g_simple_async_result_set_op_res_gpointer (
1439 simple, context, (GDestroyNotify) async_context_free);
1440
1441 g_simple_async_result_run_in_thread (
1442 simple, mail_folder_remove_attachments_thread,
1443 io_priority, cancellable);
1444
1445 g_object_unref (simple);
1446 }
1447
1448 gboolean
1449 e_mail_folder_remove_attachments_finish (CamelFolder *folder,
1450 GAsyncResult *result,
1451 GError **error)
1452 {
1453 GSimpleAsyncResult *simple;
1454
1455 g_return_val_if_fail (
1456 g_simple_async_result_is_valid (
1457 result, G_OBJECT (folder),
1458 e_mail_folder_remove_attachments), FALSE);
1459
1460 simple = G_SIMPLE_ASYNC_RESULT (result);
1461
1462 /* Assume success unless a GError is set. */
1463 return !g_simple_async_result_propagate_error (simple, error);
1464 }
1465
1466 static void
1467 mail_folder_save_messages_thread (GSimpleAsyncResult *simple,
1468 GObject *object,
1469 GCancellable *cancellable)
1470 {
1471 AsyncContext *context;
1472 GError *error = NULL;
1473
1474 context = g_simple_async_result_get_op_res_gpointer (simple);
1475
1476 e_mail_folder_save_messages_sync (
1477 CAMEL_FOLDER (object), context->ptr_array,
1478 context->destination, cancellable, &error);
1479
1480 if (error != NULL)
1481 g_simple_async_result_take_error (simple, error);
1482 }
1483
1484 /* Helper for e_mail_folder_save_messages_sync() */
1485 static void
1486 mail_folder_save_prepare_part (CamelMimePart *mime_part)
1487 {
1488 CamelDataWrapper *content;
1489
1490 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1491
1492 if (content == NULL)
1493 return;
1494
1495 if (CAMEL_IS_MULTIPART (content)) {
1496 guint n_parts, ii;
1497
1498 n_parts = camel_multipart_get_number (
1499 CAMEL_MULTIPART (content));
1500 for (ii = 0; ii < n_parts; ii++) {
1501 mime_part = camel_multipart_get_part (
1502 CAMEL_MULTIPART (content), ii);
1503 mail_folder_save_prepare_part (mime_part);
1504 }
1505
1506 } else if (CAMEL_IS_MIME_MESSAGE (content)) {
1507 mail_folder_save_prepare_part (CAMEL_MIME_PART (content));
1508
1509 } else {
1510 CamelContentType *type;
1511
1512 /* Save textual parts as 8-bit, not encoded. */
1513 type = camel_data_wrapper_get_mime_type_field (content);
1514 if (camel_content_type_is (type, "text", "*"))
1515 camel_mime_part_set_encoding (
1516 mime_part, CAMEL_TRANSFER_ENCODING_8BIT);
1517 }
1518 }
1519
1520 gboolean
1521 e_mail_folder_save_messages_sync (CamelFolder *folder,
1522 GPtrArray *message_uids,
1523 GFile *destination,
1524 GCancellable *cancellable,
1525 GError **error)
1526 {
1527 GFileOutputStream *file_output_stream;
1528 CamelStream *base_stream = NULL;
1529 GByteArray *byte_array;
1530 gboolean success = TRUE;
1531 guint ii;
1532
1533 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1534 g_return_val_if_fail (message_uids != NULL, FALSE);
1535 g_return_val_if_fail (G_IS_FILE (destination), FALSE);
1536
1537 /* Need at least one message UID to save. */
1538 g_return_val_if_fail (message_uids->len > 0, FALSE);
1539
1540 camel_operation_push_message (
1541 cancellable, ngettext (
1542 "Saving %d message",
1543 "Saving %d messages",
1544 message_uids->len),
1545 message_uids->len);
1546
1547 file_output_stream = g_file_replace (
1548 destination, NULL, FALSE,
1549 G_FILE_CREATE_PRIVATE |
1550 G_FILE_CREATE_REPLACE_DESTINATION,
1551 cancellable, error);
1552
1553 if (file_output_stream == NULL) {
1554 camel_operation_pop_message (cancellable);
1555 return FALSE;
1556 }
1557
1558 byte_array = g_byte_array_new ();
1559
1560 for (ii = 0; ii < message_uids->len; ii++) {
1561 CamelMimeMessage *message;
1562 CamelMimeFilter *filter;
1563 CamelStream *stream;
1564 const gchar *uid;
1565 gchar *from_line;
1566 gint percent;
1567 gint retval;
1568
1569 if (base_stream != NULL)
1570 g_object_unref (base_stream);
1571
1572 /* CamelStreamMem does NOT take ownership of the byte
1573 * array when set with camel_stream_mem_set_byte_array().
1574 * This allows us to reuse the same memory slab for each
1575 * message, which is slightly more efficient. */
1576 base_stream = camel_stream_mem_new ();
1577 camel_stream_mem_set_byte_array (
1578 CAMEL_STREAM_MEM (base_stream), byte_array);
1579
1580 uid = g_ptr_array_index (message_uids, ii);
1581
1582 message = camel_folder_get_message_sync (
1583 folder, uid, cancellable, error);
1584 if (message == NULL) {
1585 success = FALSE;
1586 goto exit;
1587 }
1588
1589 mail_folder_save_prepare_part (CAMEL_MIME_PART (message));
1590
1591 from_line = camel_mime_message_build_mbox_from (message);
1592 g_return_val_if_fail (from_line != NULL, FALSE);
1593
1594 success = g_output_stream_write_all (
1595 G_OUTPUT_STREAM (file_output_stream),
1596 from_line, strlen (from_line), NULL,
1597 cancellable, error);
1598
1599 g_free (from_line);
1600
1601 if (!success) {
1602 g_object_unref (message);
1603 goto exit;
1604 }
1605
1606 filter = camel_mime_filter_from_new ();
1607 stream = camel_stream_filter_new (base_stream);
1608 camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
1609
1610 retval = camel_data_wrapper_write_to_stream_sync (
1611 CAMEL_DATA_WRAPPER (message),
1612 stream, cancellable, error);
1613
1614 g_object_unref (filter);
1615 g_object_unref (stream);
1616
1617 if (retval == -1) {
1618 g_object_unref (message);
1619 goto exit;
1620 }
1621
1622 g_byte_array_append (byte_array, (guint8 *) "\n", 1);
1623
1624 success = g_output_stream_write_all (
1625 G_OUTPUT_STREAM (file_output_stream),
1626 byte_array->data, byte_array->len,
1627 NULL, cancellable, error);
1628
1629 if (!success) {
1630 g_object_unref (message);
1631 goto exit;
1632 }
1633
1634 percent = ((ii + 1) * 100) / message_uids->len;
1635 camel_operation_progress (cancellable, percent);
1636
1637 /* Reset the byte array for the next message. */
1638 g_byte_array_set_size (byte_array, 0);
1639
1640 g_object_unref (message);
1641 }
1642
1643 exit:
1644 if (base_stream != NULL)
1645 g_object_unref (base_stream);
1646
1647 g_byte_array_free (byte_array, TRUE);
1648
1649 g_object_unref (file_output_stream);
1650
1651 camel_operation_pop_message (cancellable);
1652
1653 if (!success) {
1654 /* Try deleting the destination file. */
1655 g_file_delete (destination, NULL, NULL);
1656 }
1657
1658 return success;
1659 }
1660
1661 void
1662 e_mail_folder_save_messages (CamelFolder *folder,
1663 GPtrArray *message_uids,
1664 GFile *destination,
1665 gint io_priority,
1666 GCancellable *cancellable,
1667 GAsyncReadyCallback callback,
1668 gpointer user_data)
1669 {
1670 GSimpleAsyncResult *simple;
1671 AsyncContext *context;
1672
1673 g_return_if_fail (CAMEL_IS_FOLDER (folder));
1674 g_return_if_fail (message_uids != NULL);
1675 g_return_if_fail (G_IS_FILE (destination));
1676
1677 /* Need at least one message UID to save. */
1678 g_return_if_fail (message_uids->len > 0);
1679
1680 context = g_slice_new0 (AsyncContext);
1681 context->ptr_array = g_ptr_array_ref (message_uids);
1682 context->destination = g_object_ref (destination);
1683
1684 simple = g_simple_async_result_new (
1685 G_OBJECT (folder), callback, user_data,
1686 e_mail_folder_save_messages);
1687
1688 g_simple_async_result_set_check_cancellable (simple, cancellable);
1689
1690 g_simple_async_result_set_op_res_gpointer (
1691 simple, context, (GDestroyNotify) async_context_free);
1692
1693 g_simple_async_result_run_in_thread (
1694 simple, mail_folder_save_messages_thread,
1695 io_priority, cancellable);
1696
1697 g_object_unref (simple);
1698 }
1699
1700 gboolean
1701 e_mail_folder_save_messages_finish (CamelFolder *folder,
1702 GAsyncResult *result,
1703 GError **error)
1704 {
1705 GSimpleAsyncResult *simple;
1706
1707 g_return_val_if_fail (
1708 g_simple_async_result_is_valid (
1709 result, G_OBJECT (folder),
1710 e_mail_folder_save_messages), FALSE);
1711
1712 simple = G_SIMPLE_ASYNC_RESULT (result);
1713
1714 /* Assume success unless a GError is set. */
1715 return !g_simple_async_result_propagate_error (simple, error);
1716 }
1717
1718 /**
1719 * e_mail_folder_uri_build:
1720 * @store: a #CamelStore
1721 * @folder_name: a folder name
1722 *
1723 * Builds a folder URI string from @store and @folder_name.
1724 *
1725 * Returns: a newly-allocated folder URI string
1726 **/
1727 gchar *
1728 e_mail_folder_uri_build (CamelStore *store,
1729 const gchar *folder_name)
1730 {
1731 const gchar *uid;
1732 gchar *encoded_name;
1733 gchar *encoded_uid;
1734 gchar *uri;
1735
1736 g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
1737 g_return_val_if_fail (folder_name != NULL, NULL);
1738
1739 /* Skip the leading slash, if present. */
1740 if (*folder_name == '/')
1741 folder_name++;
1742
1743 uid = camel_service_get_uid (CAMEL_SERVICE (store));
1744
1745 encoded_uid = camel_url_encode (uid, ":;@/");
1746 encoded_name = camel_url_encode (folder_name, "#");
1747
1748 uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
1749
1750 g_free (encoded_uid);
1751 g_free (encoded_name);
1752
1753 return uri;
1754 }
1755
1756 /**
1757 * e_mail_folder_uri_parse:
1758 * @session: a #CamelSession
1759 * @folder_uri: a folder URI
1760 * @out_store: return location for a #CamelStore, or %NULL
1761 * @out_folder_name: return location for a folder name, or %NULL
1762 * @error: return location for a #GError, or %NULL
1763 *
1764 * Parses a folder URI generated by e_mail_folder_uri_build() and
1765 * returns the corresponding #CamelStore instance in @out_store and
1766 * folder name string in @out_folder_name. If the URI is malformed
1767 * or no corresponding store exists, the function sets @error and
1768 * returns %FALSE.
1769 *
1770 * If the function is able to parse the URI, the #CamelStore instance
1771 * set in @out_store should be unreferenced with g_object_unref() when
1772 * done with it, and the folder name string set in @out_folder_name
1773 * should be freed with g_free().
1774 *
1775 * The function also handles older style URIs, such as ones where the
1776 * #CamelStore's #CamelStore::uri string was embedded directly in the
1777 * folder URI, and account-based URIs that used an "email://" prefix.
1778 *
1779 * Returns: %TRUE if @folder_uri could be parsed, %FALSE otherwise
1780 **/
1781 gboolean
1782 e_mail_folder_uri_parse (CamelSession *session,
1783 const gchar *folder_uri,
1784 CamelStore **out_store,
1785 gchar **out_folder_name,
1786 GError **error)
1787 {
1788 CamelURL *url;
1789 CamelService *service = NULL;
1790 gchar *folder_name = NULL;
1791 gboolean success = FALSE;
1792
1793 g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1794 g_return_val_if_fail (folder_uri != NULL, FALSE);
1795
1796 url = camel_url_new (folder_uri, error);
1797 if (url == NULL)
1798 return FALSE;
1799
1800 /* Current URI Format: 'folder://' STORE_UID '/' FOLDER_PATH */
1801 if (g_strcmp0 (url->protocol, "folder") == 0) {
1802
1803 if (url->host != NULL) {
1804 gchar *uid;
1805
1806 if (url->user == NULL || *url->user == '\0')
1807 uid = g_strdup (url->host);
1808 else
1809 uid = g_strconcat (
1810 url->user, "@", url->host, NULL);
1811
1812 service = camel_session_ref_service (session, uid);
1813 g_free (uid);
1814 }
1815
1816 if (url->path != NULL && *url->path == '/')
1817 folder_name = camel_url_decode_path (url->path + 1);
1818
1819 /* This style was used to reference accounts by UID before
1820 * CamelServices themselves had UIDs. Some examples are:
1821 *
1822 * Special cases:
1823 *
1824 * 'email://local@local/' FOLDER_PATH
1825 * 'email://vfolder@local/' FOLDER_PATH
1826 *
1827 * General case:
1828 *
1829 * 'email://' ACCOUNT_UID '/' FOLDER_PATH
1830 *
1831 * Note: ACCOUNT_UID is now equivalent to STORE_UID, and
1832 * the STORE_UIDs for the special cases are 'local'
1833 * and 'vfolder'.
1834 */
1835 } else if (g_strcmp0 (url->protocol, "email") == 0) {
1836 gchar *uid = NULL;
1837
1838 /* Handle the special cases. */
1839 if (g_strcmp0 (url->host, "local") == 0) {
1840 if (g_strcmp0 (url->user, "local") == 0)
1841 uid = g_strdup ("local");
1842 if (g_strcmp0 (url->user, "vfolder") == 0)
1843 uid = g_strdup ("vfolder");
1844 }
1845
1846 /* Handle the general case. */
1847 if (uid == NULL && url->host != NULL) {
1848 if (url->user == NULL)
1849 uid = g_strdup (url->host);
1850 else
1851 uid = g_strdup_printf (
1852 "%s@%s", url->user, url->host);
1853 }
1854
1855 if (uid != NULL) {
1856 service = camel_session_ref_service (session, uid);
1857 g_free (uid);
1858 }
1859
1860 if (url->path != NULL && *url->path == '/')
1861 folder_name = camel_url_decode_path (url->path + 1);
1862
1863 /* CamelFolderInfo URIs used to embed the store's URI, so the
1864 * folder name is appended as either a path part or a fragment
1865 * part, depending whether the store's URI used the path part.
1866 * To determine which it is, you have to check the provider
1867 * flags for CAMEL_URL_FRAGMENT_IS_PATH. */
1868 } else {
1869 service = camel_session_ref_service_by_url (
1870 session, url, CAMEL_PROVIDER_STORE);
1871
1872 if (CAMEL_IS_STORE (service)) {
1873 CamelProvider *provider;
1874
1875 provider = camel_service_get_provider (service);
1876
1877 if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
1878 folder_name = g_strdup (url->fragment);
1879 else if (url->path != NULL && *url->path == '/')
1880 folder_name = g_strdup (url->path + 1);
1881 }
1882 }
1883
1884 if (CAMEL_IS_STORE (service) && folder_name != NULL) {
1885 if (out_store != NULL)
1886 *out_store = g_object_ref (service);
1887
1888 if (out_folder_name != NULL) {
1889 *out_folder_name = folder_name;
1890 folder_name = NULL;
1891 }
1892
1893 success = TRUE;
1894 } else {
1895 g_set_error (
1896 error, CAMEL_FOLDER_ERROR,
1897 CAMEL_FOLDER_ERROR_INVALID,
1898 _("Invalid folder URI '%s'"),
1899 folder_uri);
1900 }
1901
1902 if (service != NULL)
1903 g_object_unref (service);
1904
1905 g_free (folder_name);
1906
1907 camel_url_free (url);
1908
1909 return success;
1910 }
1911
1912 /**
1913 * e_mail_folder_uri_equal:
1914 * @session: a #CamelSession
1915 * @folder_uri_a: a folder URI
1916 * @folder_uri_b: another folder URI
1917 *
1918 * Compares two folder URIs for equality. If either URI is invalid,
1919 * the function returns %FALSE.
1920 *
1921 * Returns: %TRUE if the URIs are equal, %FALSE if not
1922 **/
1923 gboolean
1924 e_mail_folder_uri_equal (CamelSession *session,
1925 const gchar *folder_uri_a,
1926 const gchar *folder_uri_b)
1927 {
1928 CamelStore *store_a;
1929 CamelStore *store_b;
1930 CamelStoreClass *class;
1931 gchar *folder_name_a;
1932 gchar *folder_name_b;
1933 gboolean success_a;
1934 gboolean success_b;
1935 gboolean equal = FALSE;
1936
1937 g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1938 g_return_val_if_fail (folder_uri_a != NULL, FALSE);
1939 g_return_val_if_fail (folder_uri_b != NULL, FALSE);
1940
1941 success_a = e_mail_folder_uri_parse (
1942 session, folder_uri_a, &store_a, &folder_name_a, NULL);
1943
1944 success_b = e_mail_folder_uri_parse (
1945 session, folder_uri_b, &store_b, &folder_name_b, NULL);
1946
1947 if (!success_a || !success_b)
1948 goto exit;
1949
1950 if (store_a != store_b)
1951 goto exit;
1952
1953 /* Doesn't matter which store we use since they're the same. */
1954 class = CAMEL_STORE_GET_CLASS (store_a);
1955 g_return_val_if_fail (class->equal_folder_name != NULL, FALSE);
1956
1957 equal = class->equal_folder_name (folder_name_a, folder_name_b);
1958
1959 exit:
1960 if (success_a) {
1961 g_object_unref (store_a);
1962 g_free (folder_name_a);
1963 }
1964
1965 if (success_b) {
1966 g_object_unref (store_b);
1967 g_free (folder_name_b);
1968 }
1969
1970 return equal;
1971 }
1972
1973 /**
1974 * e_mail_folder_uri_from_folder:
1975 * @folder: a #CamelFolder
1976 *
1977 * Convenience function for building a folder URI from a #CamelFolder.
1978 * Free the returned URI string with g_free().
1979 *
1980 * Returns: a newly-allocated folder URI string
1981 **/
1982 gchar *
1983 e_mail_folder_uri_from_folder (CamelFolder *folder)
1984 {
1985 CamelStore *store;
1986 const gchar *folder_name;
1987
1988 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
1989
1990 store = camel_folder_get_parent_store (folder);
1991 folder_name = camel_folder_get_full_name (folder);
1992
1993 return e_mail_folder_uri_build (store, folder_name);
1994 }
1995
1996 /**
1997 * e_mail_folder_uri_to_markup:
1998 * @session: a #CamelSession
1999 * @folder_uri: a folder URI
2000 * @error: return location for a #GError, or %NULL
2001 *
2002 * Converts @folder_uri to a markup string suitable for displaying to users.
2003 * The string consists of the #CamelStore display name (in bold), followed
2004 * by the folder path. If the URI is malformed or no corresponding store
2005 * exists, the function sets @error and returns %NULL. Free the returned
2006 * string with g_free().
2007 *
2008 * Returns: a newly-allocated markup string, or %NULL
2009 **/
2010 gchar *
2011 e_mail_folder_uri_to_markup (CamelSession *session,
2012 const gchar *folder_uri,
2013 GError **error)
2014 {
2015 CamelStore *store = NULL;
2016 const gchar *display_name;
2017 gchar *folder_name = NULL;
2018 gchar *markup;
2019 gboolean success;
2020
2021 g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
2022 g_return_val_if_fail (folder_uri != NULL, NULL);
2023
2024 success = e_mail_folder_uri_parse (
2025 session, folder_uri, &store, &folder_name, error);
2026
2027 if (!success)
2028 return NULL;
2029
2030 g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
2031 g_return_val_if_fail (folder_name != NULL, NULL);
2032
2033 display_name = camel_service_get_display_name (CAMEL_SERVICE (store));
2034
2035 markup = g_markup_printf_escaped (
2036 "<b>%s</b> : %s", display_name, folder_name);
2037
2038 g_object_unref (store);
2039 g_free (folder_name);
2040
2041 return markup;
2042 }