Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
mail-to-task.c:293:13 | clang-analyzer | Access to field 'mime_type' results in a dereference of a null pointer (loaded from variable 'content') | ||
mail-to-task.c:293:13 | clang-analyzer | Access to field 'mime_type' results in a dereference of a null pointer (loaded from variable 'content') |
1 /*
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with the program; if not, see <http://www.gnu.org/licenses/>
15 *
16 *
17 * Authors:
18 * Michael Zucchi <notzed@novell.com>
19 * Rodrigo Moya <rodrigo@novell.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 *
23 */
24
25 /* Convert a mail message into a task */
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <stdio.h>
32 #include <string.h>
33 #include <glib/gi18n-lib.h>
34
35 #include <libecal/libecal.h>
36 #include <libedataserverui/libedataserverui.h>
37
38 #include <libemail-engine/e-mail-utils.h>
39
40 #include <e-util/e-dialog-utils.h>
41
42 #include <misc/e-popup-action.h>
43 #include <misc/e-attachment-store.h>
44
45 #include <shell/e-shell-view.h>
46 #include <shell/e-shell-window-actions.h>
47
48 #include <mail/e-mail-browser.h>
49 #include <mail/em-utils.h>
50 #include <mail/message-list.h>
51
52 #include <calendar/gui/dialogs/comp-editor.h>
53 #include <calendar/gui/dialogs/event-editor.h>
54 #include <calendar/gui/dialogs/memo-editor.h>
55 #include <calendar/gui/dialogs/task-editor.h>
56
57 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_APPOINTMENT(window) \
58 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-appointment")
59 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING(window) \
60 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-meeting")
61 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO(window) \
62 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-memo")
63 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK(window) \
64 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-task")
65
66 gboolean mail_browser_init (GtkUIManager *ui_manager,
67 EMailBrowser *browser);
68 gboolean mail_shell_view_init (GtkUIManager *ui_manager,
69 EShellView *shell_view);
70
71 static CompEditor *
72 get_component_editor (EShell *shell,
73 ECalClient *client,
74 ECalComponent *comp,
75 gboolean is_new,
76 GError **error)
77 {
78 ECalComponentId *id;
79 CompEditorFlags flags = 0;
80 CompEditor *editor = NULL;
81 ESourceRegistry *registry;
82
83 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
84 g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
85 g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
86
87 registry = e_shell_get_registry (shell);
88
89 id = e_cal_component_get_id (comp);
90 g_return_val_if_fail (id != NULL, NULL);
91 g_return_val_if_fail (id->uid != NULL, NULL);
92
93 if (is_new) {
94 flags |= COMP_EDITOR_NEW_ITEM;
95 } else {
96 editor = comp_editor_find_instance (id->uid);
97 }
98
99 if (!editor) {
100 if (itip_organizer_is_user (registry, comp, client))
101 flags |= COMP_EDITOR_USER_ORG;
102
103 switch (e_cal_component_get_vtype (comp)) {
104 case E_CAL_COMPONENT_EVENT:
105 if (e_cal_component_has_attendees (comp))
106 flags |= COMP_EDITOR_MEETING;
107
108 editor = event_editor_new (client, shell, flags);
109
110 if (flags & COMP_EDITOR_MEETING)
111 event_editor_show_meeting (EVENT_EDITOR (editor));
112 break;
113 case E_CAL_COMPONENT_TODO:
114 if (e_cal_component_has_attendees (comp))
115 flags |= COMP_EDITOR_IS_ASSIGNED;
116
117 editor = task_editor_new (client, shell, flags);
118
119 if (flags & COMP_EDITOR_IS_ASSIGNED)
120 task_editor_show_assignment (TASK_EDITOR (editor));
121 break;
122 case E_CAL_COMPONENT_JOURNAL:
123 if (e_cal_component_has_organizer (comp))
124 flags |= COMP_EDITOR_IS_SHARED;
125
126 editor = memo_editor_new (client, shell, flags);
127 break;
128 default:
129 if (error)
130 *error = e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL);
131 break;
132 }
133
134 if (editor) {
135 comp_editor_edit_comp (editor, comp);
136
137 /* request save for new events */
138 comp_editor_set_changed (editor, is_new);
139 }
140 }
141
142 e_cal_component_free_id (id);
143
144 return editor;
145 }
146
147 static void
148 set_attendees (ECalComponent *comp,
149 CamelMimeMessage *message,
150 const gchar *organizer)
151 {
152 GSList *attendees = NULL, *to_free = NULL;
153 ECalComponentAttendee *ca;
154 CamelInternetAddress *from = NULL, *to, *cc, *bcc, *arr[4];
155 gint len, i, j;
156
157 if (message->reply_to)
158 from = message->reply_to;
159 else if (message->from)
160 from = message->from;
161
162 to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
163 cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
164 bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
165
166 arr[0] = from; arr[1] = to; arr[2] = cc; arr[3] = bcc;
167
168 for (j = 0; j < 4; j++) {
169 if (!arr[j])
170 continue;
171
172 len = CAMEL_ADDRESS (arr[j])->addresses->len;
173 for (i = 0; i < len; i++) {
174 const gchar *name, *addr;
175
176 if (camel_internet_address_get (arr[j], i, &name, &addr)) {
177 gchar *temp;
178
179 temp = g_strconcat ("mailto:", addr, NULL);
180 if (organizer && g_ascii_strcasecmp (temp, organizer) == 0) {
181 /* do not add organizer twice */
182 g_free (temp);
183 continue;
184 }
185
186 ca = g_new0 (ECalComponentAttendee, 1);
187
188 ca->value = temp;
189 ca->cn = name;
190 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
191 ca->status = ICAL_PARTSTAT_NEEDSACTION;
192 if (j == 0) {
193 /* From */
194 ca->role = ICAL_ROLE_CHAIR;
195 } else if (j == 2) {
196 /* BCC */
197 ca->role = ICAL_ROLE_OPTPARTICIPANT;
198 } else {
199 /* all other */
200 ca->role = ICAL_ROLE_REQPARTICIPANT;
201 }
202
203 to_free = g_slist_prepend (to_free, temp);
204
205 attendees = g_slist_append (attendees, ca);
206 }
207 }
208 }
209
210 e_cal_component_set_attendee_list (comp, attendees);
211
212 g_slist_foreach (attendees, (GFunc) g_free, NULL);
213 g_slist_foreach (to_free, (GFunc) g_free, NULL);
214
215 g_slist_free (to_free);
216 g_slist_free (attendees);
217 }
218
219 static const gchar *
220 prepend_from (CamelMimeMessage *message,
221 gchar **text)
222 {
223 gchar *res, *tmp, *addr = NULL;
224 const gchar *name = NULL, *eml = NULL;
225 CamelInternetAddress *from = NULL;
226
227 g_return_val_if_fail (message != NULL, NULL);
228 g_return_val_if_fail (text != NULL, NULL);
229
230 if (message->reply_to)
231 from = message->reply_to;
232 else if (message->from)
233 from = message->from;
234
235 if (from && camel_internet_address_get (from, 0, &name, &eml))
236 addr = camel_internet_address_format_address (name, eml);
237
238 /* To Translators: The full sentence looks like: "Created from a mail by John Doe <john.doe@myco.example>" */
239 tmp = g_strdup_printf (_("Created from a mail by %s"), addr ? addr : "");
240
241 res = g_strconcat (tmp, "\n", *text, NULL);
242
243 g_free (tmp);
244 g_free (*text);
245
246 *text = res;
247
248 return res;
249 }
250
251 static void
252 set_description (ECalComponent *comp,
253 CamelMimeMessage *message)
254 {
255 CamelDataWrapper *content;
256 CamelStream *stream;
257 CamelContentType *type;
258 CamelMimePart *mime_part = CAMEL_MIME_PART (message);
259 ECalComponentText *text = NULL;
260 GByteArray *byte_array;
261 GSList *sl = NULL;
262 gchar *str, *convert_str = NULL;
263 gsize bytes_read, bytes_written;
264 gint count = 2;
265
266 content = camel_medium_get_content ((CamelMedium *) message);
267 if (!content)
268 return;
269
270 /*
271 * Get non-multipart content from multipart message.
272 */
273 while (CAMEL_IS_MULTIPART (content) && count > 0) {
274 mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), 0);
275 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
276 count--;
277 }
278
279 if (!mime_part)
280 return;
281
282 type = camel_mime_part_get_content_type (mime_part);
283 if (!camel_content_type_is (type, "text", "plain"))
284 return;
285
286 byte_array = g_byte_array_new ();
287 stream = camel_stream_mem_new_with_byte_array (byte_array);
288 camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL);
289 str = g_strndup ((gchar *) byte_array->data, byte_array->len);
290 g_object_unref (stream);
291
292 /* convert to UTF-8 string */
293 if (str && content->mime_type->params && content->mime_type->params->value) {
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
294 convert_str = g_convert (
295 str, strlen (str),
296 "UTF-8", content->mime_type->params->value,
297 &bytes_read, &bytes_written, NULL);
298 }
299
300 text = g_new0 (ECalComponentText, 1);
301 if (convert_str)
302 text->value = prepend_from (message, &convert_str);
303 else
304 text->value = prepend_from (message, &str);
305 text->altrep = NULL;
306 sl = g_slist_append (sl, text);
307
308 e_cal_component_set_description_list (comp, sl);
309
310 g_free (str);
311 if (convert_str)
312 g_free (convert_str);
313 e_cal_component_free_text_list (sl);
314 }
315
316 static gchar *
317 set_organizer (ECalComponent *comp,
318 CamelFolder *folder)
319 {
320 EShell *shell;
321 ESource *source = NULL;
322 ESourceRegistry *registry;
323 ESourceMailIdentity *extension;
324 const gchar *extension_name;
325 const gchar *address, *name;
326 ECalComponentOrganizer organizer = {NULL, NULL, NULL, NULL};
327 gchar *mailto = NULL;
328
329 shell = e_shell_get_default ();
330 registry = e_shell_get_registry (shell);
331
332 if (folder != NULL) {
333 CamelStore *store;
334
335 store = camel_folder_get_parent_store (folder);
336 source = em_utils_ref_mail_identity_for_store (registry, store);
337 }
338
339 if (source == NULL)
340 source = e_source_registry_ref_default_mail_identity (registry);
341
342 g_return_val_if_fail (source != NULL, NULL);
343
344 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
345 extension = e_source_get_extension (source, extension_name);
346
347 name = e_source_mail_identity_get_name (extension);
348 address = e_source_mail_identity_get_address (extension);
349
350 if (name != NULL && address != NULL) {
351 mailto = g_strconcat ("mailto:", address, NULL);
352 organizer.value = mailto;
353 organizer.cn = name;
354 e_cal_component_set_organizer (comp, &organizer);
355 }
356
357 g_object_unref (source);
358
359 return mailto;
360 }
361
362 struct _att_async_cb_data {
363 gchar **uris;
364 EFlag *flag;
365 };
366
367 static void
368 attachment_load_finished (EAttachmentStore *store,
369 GAsyncResult *result,
370 gpointer user_data)
371 {
372 struct _att_async_cb_data *data = user_data;
373
374 /* XXX Should be no need to check for error here.
375 * This is just to reset state in the EAttachment. */
376 e_attachment_store_load_finish (store, result, NULL);
377
378 e_flag_set (data->flag);
379 }
380
381 static void
382 attachment_save_finished (EAttachmentStore *store,
383 GAsyncResult *result,
384 gpointer user_data)
385 {
386 struct _att_async_cb_data *data = user_data;
387 gchar **uris;
388 GError *error = NULL;
389
390 uris = e_attachment_store_save_finish (store, result, &error);
391 if (error)
392 data->uris = NULL;
393 else
394 data->uris = uris;
395
396 g_clear_error (&error);
397
398 e_flag_set (data->flag);
399 }
400
401 static void
402 set_attachments (ECalClient *client,
403 ECalComponent *comp,
404 CamelMimeMessage *message)
405 {
406 /* XXX Much of this is copied from CompEditor::get_attachment_list().
407 * Perhaps it should be split off as a separate utility? */
408
409 EAttachmentStore *store;
410 CamelDataWrapper *content;
411 CamelMultipart *multipart;
412 GFile *destination;
413 GList *attachment_list = NULL;
414 GSList *uri_list = NULL;
415 const gchar *comp_uid = NULL;
416 const gchar *local_store;
417 gchar *filename_prefix, *tmp;
418 gint ii, n_parts;
419 struct _att_async_cb_data cb_data;
420
421 cb_data.flag = e_flag_new ();
422 cb_data.uris = NULL;
423
424 content = camel_medium_get_content ((CamelMedium *) message);
425 if (!content || !CAMEL_IS_MULTIPART (content))
426 return;
427
428 n_parts = camel_multipart_get_number (CAMEL_MULTIPART (content));
429 if (n_parts < 1)
430 return;
431
432 e_cal_component_get_uid (comp, &comp_uid);
433 g_return_if_fail (comp_uid != NULL);
434
435 tmp = g_strdup (comp_uid);
436 e_filename_make_safe (tmp);
437 filename_prefix = g_strconcat (tmp, "-", NULL);
438 g_free (tmp);
439
440 local_store = e_cal_client_get_local_attachment_store (client);
441 destination = g_file_new_for_path (local_store);
442
443 /* Create EAttachments from the MIME parts and add them to the
444 * attachment store. */
445
446 multipart = CAMEL_MULTIPART (content);
447 store = E_ATTACHMENT_STORE (e_attachment_store_new ());
448
449 for (ii = 1; ii < n_parts; ii++) {
450 EAttachment *attachment;
451 CamelMimePart *mime_part;
452
453 attachment = e_attachment_new ();
454 mime_part = camel_multipart_get_part (multipart, ii);
455 e_attachment_set_mime_part (attachment, mime_part);
456
457 attachment_list = g_list_append (attachment_list, attachment);
458 }
459
460 e_flag_clear (cb_data.flag);
461
462 e_attachment_store_load_async (
463 store, attachment_list, (GAsyncReadyCallback)
464 attachment_load_finished, &cb_data);
465
466 /* Loading should be instantaneous since we already have
467 * the full content, but we need to wait for the callback.
468 */
469 e_flag_wait (cb_data.flag);
470
471 g_list_foreach (attachment_list, (GFunc) g_object_unref, NULL);
472 g_list_free (attachment_list);
473
474 cb_data.uris = NULL;
475 e_flag_clear (cb_data.flag);
476
477 e_attachment_store_save_async (
478 store, destination, filename_prefix,
479 (GAsyncReadyCallback) attachment_save_finished, &cb_data);
480
481 g_free (filename_prefix);
482
483 /* We can't return until we have results. */
484 e_flag_wait (cb_data.flag);
485
486 if (cb_data.uris == NULL) {
487 e_flag_free (cb_data.flag);
488 g_warning ("No attachment URIs retrieved.");
489 return;
490 }
491
492 /* Transfer the URI strings to the GSList. */
493 for (ii = 0; cb_data.uris[ii] != NULL; ii++) {
494 uri_list = g_slist_prepend (uri_list, cb_data.uris[ii]);
495 cb_data.uris[ii] = NULL;
496 }
497
498 e_flag_free (cb_data.flag);
499 g_free (cb_data.uris);
500
501 /* XXX Does this take ownership of the list? */
502 e_cal_component_set_attachment_list (comp, uri_list);
503
504 e_attachment_store_remove_all (store);
505 g_object_unref (destination);
506 g_object_unref (store);
507 }
508
509 static void
510 set_priority (ECalComponent *comp,
511 CamelMimePart *part)
512 {
513 const gchar *prio;
514
515 g_return_if_fail (comp != NULL);
516 g_return_if_fail (part != NULL);
517
518 prio = camel_header_raw_find (& (part->headers), "X-Priority", NULL);
519 if (prio && atoi (prio) > 0) {
520 gint priority = 1;
521
522 e_cal_component_set_priority (comp, &priority);
523 }
524 }
525
526 struct _report_error
527 {
528 gchar *format;
529 gchar *param;
530 };
531
532 static gboolean
533 do_report_error (struct _report_error *err)
534 {
535 if (err) {
536 e_notice (NULL, GTK_MESSAGE_ERROR, err->format, err->param);
537 g_free (err->format);
538 g_free (err->param);
539 g_free (err);
540 }
541
542 return FALSE;
543 }
544
545 static void
546 report_error_idle (const gchar *format,
547 const gchar *param)
548 {
549 struct _report_error *err = g_new (struct _report_error, 1);
550
551 err->format = g_strdup (format);
552 err->param = g_strdup (param);
553
554 g_usleep (250);
555 g_idle_add ((GSourceFunc) do_report_error, err);
556 }
557
558 struct _manage_comp
559 {
560 ECalClient *client;
561 ECalComponent *comp;
562 icalcomponent *stored_comp; /* the one in client already */
563 GCond *cond;
564 GMutex *mutex;
565 gint mails_count;
566 gint mails_done;
567 gchar *editor_title;
568 gboolean can_continue;
569 };
570
571 static void
572 free_manage_comp_struct (struct _manage_comp *mc)
573 {
574 g_return_if_fail (mc != NULL);
575
576 g_object_unref (mc->comp);
577 g_object_unref (mc->client);
578 if (mc->stored_comp)
579 icalcomponent_free (mc->stored_comp);
580 if (mc->mutex)
581 g_mutex_free (mc->mutex);
582 if (mc->cond)
583 g_cond_free (mc->cond);
584 if (mc->editor_title)
585 g_free (mc->editor_title);
586
587 g_free (mc);
588 }
589
590 static gint
591 do_ask (const gchar *text,
592 gboolean is_create_edit_add)
593 {
594 gint res;
595 GtkWidget *dialog = gtk_message_dialog_new (
596 NULL,
597 GTK_DIALOG_MODAL,
598 GTK_MESSAGE_QUESTION,
599 is_create_edit_add ? GTK_BUTTONS_NONE : GTK_BUTTONS_YES_NO,
600 "%s", text);
601
602 if (is_create_edit_add) {
603 gtk_dialog_add_buttons (
604 GTK_DIALOG (dialog),
605 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
606 GTK_STOCK_EDIT, GTK_RESPONSE_YES,
607 GTK_STOCK_NEW, GTK_RESPONSE_NO,
608 NULL);
609 }
610
611 res = gtk_dialog_run (GTK_DIALOG (dialog));
612
613 gtk_widget_destroy (dialog);
614
615 return res;
616 }
617
618 static const gchar *
619 get_question_edit_old (ECalClientSourceType source_type)
620 {
621 const gchar *ask = NULL;
622
623 switch (source_type) {
624 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
625 ask = _("Selected calendar contains event '%s' already. Would you like to edit the old event?");
626 break;
627 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
628 ask = _("Selected task list contains task '%s' already. Would you like to edit the old task?");
629 break;
630 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
631 ask = _("Selected memo list contains memo '%s' already. Would you like to edit the old memo?");
632 break;
633 default:
634 g_assert_not_reached ();
635 break;
636 }
637
638 return ask;
639 }
640
641 static const gchar *
642 get_question_add_all_mails (ECalClientSourceType source_type,
643 gint count)
644 {
645 const gchar *ask = NULL;
646
647 switch (source_type) {
648 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
649 /* Translators: Note there are always more than 10 mails selected */
650 ask = ngettext (
651 "You have selected %d mails to be converted to events. Do you really want to add them all?",
652 "You have selected %d mails to be converted to events. Do you really want to add them all?",
653 count);
654 break;
655 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
656 /* Translators: Note there are always more than 10 mails selected */
657 ask = ngettext (
658 "You have selected %d mails to be converted to tasks. Do you really want to add them all?",
659 "You have selected %d mails to be converted to tasks. Do you really want to add them all?",
660 count);
661 break;
662 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
663 /* Translators: Note there are always more than 10 mails selected */
664 ask = ngettext (
665 "You have selected %d mails to be converted to memos. Do you really want to add them all?",
666 "You have selected %d mails to be converted to memos. Do you really want to add them all?",
667 count);
668 break;
669 default:
670 g_assert_not_reached ();
671 break;
672 }
673
674 return ask;
675 }
676
677 static void
678 comp_editor_closed (CompEditor *editor,
679 gboolean accepted,
680 struct _manage_comp *mc)
681 {
682 if (!mc)
683 return;
684
685 if (!accepted && mc->mails_done < mc->mails_count)
686 mc->can_continue = (do_ask (_("Do you wish to continue converting remaining mails?"), FALSE) == GTK_RESPONSE_YES);
687
688 /* Signal the do_mail_to_event thread that editor was closed and editor
689 * for next event can be displayed (if any) */
690 g_cond_signal (mc->cond);
691 }
692
693 /*
694 * This handler takes title of the editor window and
695 * inserts information about number of processed mails and
696 * number of all mails to process, so the window title
697 * will look like "Appointment (3/10) - An appoitment name"
698 */
699 static void
700 comp_editor_title_changed (GtkWidget *widget,
701 GParamSpec *pspec,
702 struct _manage_comp *mc)
703 {
704 GtkWindow *editor = GTK_WINDOW (widget);
705 const gchar *title = gtk_window_get_title (editor);
706 gchar *new_title;
707 gchar *splitter;
708 gchar *comp_name, *task_name;
709
710 if (!mc)
711 return;
712
713 /* Recursion prevence */
714 if (mc->editor_title && g_utf8_collate (mc->editor_title, title) == 0)
715 return;
716
717 splitter = strchr (title, '-');
718 if (!splitter)
719 return;
720
721 comp_name = g_strndup (title, splitter - title - 1);
722 task_name = g_strdup (splitter + 2);
723 new_title = g_strdup_printf (
724 "%s (%d/%d) - %s",
725 comp_name, mc->mails_done, mc->mails_count, task_name);
726
727 /* Remember the new title, so that when gtk_window_set_title() causes
728 * this handler to be recursively called, we can recognize that and
729 * prevent endless recursion */
730 if (mc->editor_title)
731 g_free (mc->editor_title);
732 mc->editor_title = new_title;
733
734 gtk_window_set_title (editor, new_title);
735
736 g_free (comp_name);
737 g_free (task_name);
738 }
739
740 static gboolean
741 do_manage_comp_idle (struct _manage_comp *mc)
742 {
743 GError *error = NULL;
744 ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST;
745 ECalComponent *edit_comp = NULL;
746
747 g_return_val_if_fail (mc, FALSE);
748
749 source_type = e_cal_client_get_source_type (mc->client);
750
751 if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) {
752 free_manage_comp_struct (mc);
753
754 g_warning ("mail-to-task: Incorrect call of %s, no data given", G_STRFUNC);
755 return FALSE;
756 }
757
758 if (mc->stored_comp) {
759 const gchar *ask = get_question_edit_old (source_type);
760
761 if (ask) {
762 gchar *msg = g_strdup_printf (ask, icalcomponent_get_summary (mc->stored_comp) ? icalcomponent_get_summary (mc->stored_comp) : _("[No Summary]"));
763 gint chosen;
764
765 chosen = do_ask (msg, TRUE);
766
767 if (chosen == GTK_RESPONSE_YES) {
768 edit_comp = e_cal_component_new ();
769 if (!e_cal_component_set_icalcomponent (edit_comp, icalcomponent_new_clone (mc->stored_comp))) {
770 g_object_unref (edit_comp);
771 edit_comp = NULL;
772 error = g_error_new (
773 E_CAL_CLIENT_ERROR,
774 E_CAL_CLIENT_ERROR_INVALID_OBJECT,
775 "%s", _("Invalid object returned from a server"));
776
777 }
778 } else if (chosen == GTK_RESPONSE_NO) {
779 /* user wants to create a new event, thus generate a new UID */
780 gchar *new_uid = e_cal_component_gen_uid ();
781 edit_comp = mc->comp;
782 e_cal_component_set_uid (edit_comp, new_uid);
783 e_cal_component_set_recurid (edit_comp, NULL);
784 g_free (new_uid);
785 }
786 g_free (msg);
787 }
788 } else {
789 edit_comp = mc->comp;
790 }
791
792 if (edit_comp) {
793 EShell *shell;
794 CompEditor *editor;
795
796 /* FIXME Pass in the EShell instance. */
797 shell = e_shell_get_default ();
798 editor = get_component_editor (
799 shell, mc->client, edit_comp,
800 edit_comp == mc->comp, &error);
801
802 if (editor && !error) {
803 /* Force editor's title change */
804 comp_editor_title_changed (GTK_WIDGET (editor), NULL, mc);
805
806 g_signal_connect (
807 editor, "notify::title",
808 G_CALLBACK (comp_editor_title_changed), mc);
809 g_signal_connect (
810 editor, "comp_closed",
811 G_CALLBACK (comp_editor_closed), mc);
812
813 gtk_window_present (GTK_WINDOW (editor));
814
815 if (edit_comp != mc->comp)
816 g_object_unref (edit_comp);
817 } else {
818 g_warning ("Failed to create event editor: %s", error ? error->message : "Unknown error");
819 g_cond_signal (mc->cond);
820 }
821 } else {
822 /* User canceled editing already existing event, so treat it as if he just closed the editor window */
823 comp_editor_closed (NULL, FALSE, mc);
824 }
825
826 if (error) {
827 e_notice (NULL, GTK_MESSAGE_ERROR, _("An error occurred during processing: %s"), error->message);
828 g_clear_error (&error);
829 }
830
831 return FALSE;
832 }
833
834 typedef struct {
835 ECalClient *client;
836 CamelFolder *folder;
837 GPtrArray *uids;
838 gchar *selected_text;
839 gboolean with_attendees;
840 }AsyncData;
841
842 static gboolean
843 do_mail_to_event (AsyncData *data)
844 {
845 ECalClient *client = data->client;
846 CamelFolder *folder = data->folder;
847 GPtrArray *uids = data->uids;
848 GError *err = NULL;
849
850 /* open the task client */
851 e_client_open_sync (E_CLIENT (client), FALSE, NULL, &err);
852
853 if (err != NULL) {
854 report_error_idle (_("Cannot open calendar. %s"), err->message);
855 } else if (e_client_is_readonly (E_CLIENT (client))) {
856 if (err)
857 report_error_idle ("Check readonly failed. %s", err->message);
858 else {
859 switch (e_cal_client_get_source_type (client)) {
860 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
861 report_error_idle (_("Selected source is read only, thus cannot create event there. Select other source, please."), NULL);
862 break;
863 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
864 report_error_idle (_("Selected source is read only, thus cannot create task there. Select other source, please."), NULL);
865 break;
866 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
867 report_error_idle (_("Selected source is read only, thus cannot create memo there. Select other source, please."), NULL);
868 break;
869 default:
870 g_assert_not_reached ();
871 break;
872 }
873 }
874 } else {
875 gint i;
876 ECalClientSourceType source_type = e_cal_client_get_source_type (client);
877 ECalComponentDateTime dt, dt2;
878 struct icaltimetype tt, tt2;
879 struct _manage_comp *oldmc = NULL;
880
881 #define cache_backend_prop(prop) { \
882 gchar *val = NULL; \
883 e_client_get_backend_property_sync (E_CLIENT (client), prop, &val, NULL, NULL); \
884 g_free (val); \
885 }
886
887 /* precache backend properties, thus editor have them ready when needed */
888 cache_backend_prop (CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS);
889 cache_backend_prop (CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS);
890 cache_backend_prop (CAL_BACKEND_PROPERTY_DEFAULT_OBJECT);
891 e_client_get_capabilities (E_CLIENT (client));
892
893 #undef cache_backend_prop
894
895 /* set start day of the event as today, without time - easier than looking for a calendar's time zone */
896 tt = icaltime_today ();
897 dt.value = &tt;
898 dt.tzid = NULL;
899
900 tt2 = tt;
901 icaltime_adjust (&tt2, 1, 0, 0, 0);
902 dt2.value = &tt2;
903 dt2.tzid = NULL;
904
905 for (i = 0; i < (uids ? uids->len : 0); i++) {
906 CamelMimeMessage *message;
907 ECalComponent *comp;
908 ECalComponentText text;
909 icalproperty *icalprop;
910 icalcomponent *icalcomp;
911 struct _manage_comp *mc;
912
913 /* retrieve the message from the CamelFolder */
914 /* FIXME Not passing a GCancellable or GError. */
915 message = camel_folder_get_message_sync (
916 folder, g_ptr_array_index (uids, i),
917 NULL, NULL);
918 if (!message) {
919 continue;
920 }
921
922 comp = e_cal_component_new ();
923
924 switch (source_type) {
925 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
926 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
927 break;
928 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
929 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
930 break;
931 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
932 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
933 break;
934 default:
935 g_assert_not_reached ();
936 break;
937 }
938
939 e_cal_component_set_uid (comp, camel_mime_message_get_message_id (message));
940 e_cal_component_set_dtstart (comp, &dt);
941
942 if (source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) {
943 /* make it an all-day event */
944 e_cal_component_set_dtend (comp, &dt2);
945 }
946
947 /* set the summary */
948 text.value = camel_mime_message_get_subject (message);
949 text.altrep = NULL;
950 e_cal_component_set_summary (comp, &text);
951
952 /* set all fields */
953 if (data->selected_text) {
954 GSList sl;
955
956 text.value = data->selected_text;
957 text.altrep = NULL;
958 sl.next = NULL;
959 sl.data = &text;
960
961 e_cal_component_set_description_list (comp, &sl);
962 } else
963 set_description (comp, message);
964
965 if (data->with_attendees) {
966 gchar *organizer;
967
968 /* set actual user as organizer, to be able to change event's properties */
969 organizer = set_organizer (comp, data->folder);
970 set_attendees (comp, message, organizer);
971 g_free (organizer);
972 }
973
974 /* set attachment files */
975 set_attachments (client, comp, message);
976
977 /* priority */
978 set_priority (comp, CAMEL_MIME_PART (message));
979
980 /* no need to increment a sequence number, this is a new component */
981 e_cal_component_abort_sequence (comp);
982
983 icalcomp = e_cal_component_get_icalcomponent (comp);
984
985 icalprop = icalproperty_new_x ("1");
986 icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR");
987 icalcomponent_add_property (icalcomp, icalprop);
988
989 mc = g_new0 (struct _manage_comp, 1);
990 mc->client = g_object_ref (client);
991 mc->comp = g_object_ref (comp);
992 mc->mutex = g_mutex_new ();
993 mc->cond = g_cond_new ();
994 mc->mails_count = uids->len;
995 mc->mails_done = i + 1; /* Current task */
996 mc->editor_title = NULL;
997 mc->can_continue = TRUE;
998
999 if (oldmc) {
1000 /* Wait for user to quit the editor created in previous iteration
1001 * before displaying next one */
1002 gboolean can_continue;
1003 g_mutex_lock (oldmc->mutex);
1004 g_cond_wait (oldmc->cond, oldmc->mutex);
1005 g_mutex_unlock (oldmc->mutex);
1006 can_continue = oldmc->can_continue;
1007 free_manage_comp_struct (oldmc);
1008 oldmc = NULL;
1009
1010 if (!can_continue)
1011 break;
1012 }
1013
1014 if (!e_cal_client_get_object_sync (client, icalcomponent_get_uid (icalcomp), NULL, &(mc->stored_comp), NULL, NULL))
1015 mc->stored_comp = NULL;
1016
1017 /* Prioritize ahead of GTK+ redraws. */
1018 g_idle_add_full (
1019 G_PRIORITY_HIGH_IDLE,
1020 (GSourceFunc) do_manage_comp_idle, mc, NULL);
1021
1022 oldmc = mc;
1023
1024 g_object_unref (comp);
1025 g_object_unref (message);
1026
1027 }
1028
1029 /* Wait for the last editor and then clean up */
1030 if (oldmc) {
1031 g_mutex_lock (oldmc->mutex);
1032 g_cond_wait (oldmc->cond, oldmc->mutex);
1033 g_mutex_unlock (oldmc->mutex);
1034 free_manage_comp_struct (oldmc);
1035 }
1036 }
1037
1038 /* free memory */
1039 g_object_unref (data->client);
1040 em_utils_uids_free (uids);
1041 g_object_unref (folder);
1042 g_free (data->selected_text);
1043 g_free (data);
1044 data = NULL;
1045
1046 if (err)
1047 g_error_free (err);
1048
1049 return TRUE;
1050 }
1051
1052 static gboolean
1053 text_contains_nonwhitespace (const gchar *text,
1054 gint len)
1055 {
1056 const gchar *p;
1057 gunichar c = 0;
1058
1059 if (!text || len <= 0)
1060 return FALSE;
1061
1062 p = text;
1063
1064 while (p && p - text < len) {
1065 c = g_utf8_get_char (p);
1066 if (!c)
1067 break;
1068
1069 if (!g_unichar_isspace (c))
1070 break;
1071
1072 p = g_utf8_next_char (p);
1073 }
1074
1075 return p - text < len - 1 && c != 0;
1076 }
1077
1078 /* should be freed with g_free after done with it */
1079 static gchar *
1080 get_selected_text (EMailReader *reader)
1081 {
1082 EMailDisplay *display;
1083 gchar *text = NULL;
1084
1085 display = e_mail_reader_get_mail_display (reader);
1086
1087 if (!e_web_view_is_selection_active (E_WEB_VIEW (display)))
1088 return NULL;
1089
1090 text = e_mail_display_get_selection_plain_text (display);
1091
1092 if (text == NULL || !text_contains_nonwhitespace (text, strlen (text))) {
1093 g_free (text);
1094 return NULL;
1095 }
1096
1097 return text;
1098 }
1099
1100 static void
1101 mail_to_event (ECalClientSourceType source_type,
1102 gboolean with_attendees,
1103 EMailReader *reader)
1104 {
1105 EShell *shell;
1106 EMailBackend *backend;
1107 ESourceRegistry *registry;
1108 CamelFolder *folder;
1109 GPtrArray *uids;
1110 ESource *source = NULL;
1111 ESource *default_source;
1112 GList *list, *iter;
1113 GtkWindow *parent;
1114 const gchar *extension_name;
1115 GError *error = NULL;
1116
1117 folder = e_mail_reader_get_folder (reader);
1118 parent = e_mail_reader_get_window (reader);
1119 uids = e_mail_reader_get_selected_uids (reader);
1120
1121 /* Ask before converting 10 or more mails to events. */
1122 if (uids->len > 10) {
1123 gchar *question;
1124 gint response;
1125
1126 question = g_strdup_printf (
1127 get_question_add_all_mails (source_type, uids->len), uids->len);
1128 response = do_ask (question, FALSE);
1129 g_free (question);
1130
1131 if (response == GTK_RESPONSE_NO) {
1132 em_utils_uids_free (uids);
1133 g_object_unref (folder);
1134 return;
1135 }
1136 }
1137
1138 backend = e_mail_reader_get_backend (reader);
1139 shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
1140 registry = e_shell_get_registry (shell);
1141
1142 switch (source_type) {
1143 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1144 extension_name = E_SOURCE_EXTENSION_CALENDAR;
1145 default_source = e_source_registry_ref_default_calendar (registry);
1146 break;
1147 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1148 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
1149 default_source = e_source_registry_ref_default_memo_list (registry);
1150 break;
1151 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1152 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
1153 default_source = e_source_registry_ref_default_task_list (registry);
1154 break;
1155 default:
1156 g_return_if_reached ();
1157 }
1158
1159 list = e_source_registry_list_sources (registry, extension_name);
1160
1161 /* If there is only one writable source, no need to prompt the user. */
1162 for (iter = list; iter != NULL; iter = g_list_next (iter)) {
1163 ESource *candidate = E_SOURCE (iter->data);
1164
1165 if (e_source_get_writable (candidate)) {
1166 if (source == NULL)
1167 source = candidate;
1168 else {
1169 source = NULL;
1170 break;
1171 }
1172 }
1173 }
1174
1175 g_list_free_full (list, (GDestroyNotify) g_object_unref);
1176
1177 if (source == NULL) {
1178 GtkWidget *dialog;
1179 ESourceSelector *selector;
1180
1181 /* ask the user which tasks list to save to */
1182 dialog = e_source_selector_dialog_new (
1183 parent, registry, extension_name);
1184
1185 selector = e_source_selector_dialog_get_selector (
1186 E_SOURCE_SELECTOR_DIALOG (dialog));
1187
1188 e_source_selector_set_primary_selection (
1189 selector, default_source);
1190
1191 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
1192 source = e_source_selector_dialog_peek_primary_selection (
1193 E_SOURCE_SELECTOR_DIALOG (dialog));
1194
1195 gtk_widget_destroy (dialog);
1196 } else if (!source && default_source) {
1197 source = default_source;
1198 } else if (!source) {
1199 e_notice (NULL, GTK_MESSAGE_ERROR, _("No writable calendar is available."));
1200
1201 em_utils_uids_free (uids);
1202 g_object_unref (folder);
1203 if (error)
1204 g_error_free (error);
1205 goto exit;
1206 }
1207
1208 if (source) {
1209 /* if a source has been selected, perform the mail2event operation */
1210 ECalClient *client = NULL;
1211 AsyncData *data = NULL;
1212 GError *error = NULL;
1213
1214 client = e_cal_client_new (source, source_type, &error);
1215 if (!client) {
1216 e_notice (
1217 parent, GTK_MESSAGE_ERROR,
1218 "Could not connect to '%s'",
1219 e_source_get_display_name (source));
1220 goto exit;
1221 }
1222
1223 /* Fill the elements in AsynData */
1224 data = g_new0 (AsyncData, 1);
1225 data->client = client;
1226 data->folder = folder;
1227 data->uids = uids;
1228 data->with_attendees = with_attendees;
1229
1230 if (uids->len == 1)
1231 data->selected_text = get_selected_text (reader);
1232 else
1233 data->selected_text = NULL;
1234
1235 g_thread_create (
1236 (GThreadFunc) do_mail_to_event, data, FALSE, &error);
1237 if (error != NULL) {
1238 g_warning (G_STRLOC ": %s", error->message);
1239 g_error_free (error);
1240 }
1241 }
1242
1243 exit:
1244 g_object_unref (default_source);
1245 }
1246
1247 static void
1248 action_mail_convert_to_event_cb (GtkAction *action,
1249 EMailReader *reader)
1250 {
1251 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE, reader);
1252 }
1253
1254 static void
1255 action_mail_convert_to_meeting_cb (GtkAction *action,
1256 EMailReader *reader)
1257 {
1258 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, TRUE, reader);
1259 }
1260
1261 static void
1262 action_mail_convert_to_memo_cb (GtkAction *action,
1263 EMailReader *reader)
1264 {
1265 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_MEMOS, FALSE, reader);
1266 }
1267
1268 static void
1269 action_mail_convert_to_task_cb (GtkAction *action,
1270 EMailReader *reader)
1271 {
1272 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_TASKS, FALSE, reader);
1273 }
1274
1275 /* Note, we're not using EPopupActions here because we update the state
1276 * of entire actions groups instead of individual actions. EPopupActions
1277 * just proxy the state of individual actions. */
1278
1279 static GtkActionEntry multi_selection_entries[] = {
1280
1281 { "mail-convert-to-appointment",
1282 "appointment-new",
1283 N_("Create an _Appointment"),
1284 NULL,
1285 N_("Create a new event from the selected message"),
1286 G_CALLBACK (action_mail_convert_to_event_cb) },
1287
1288 { "mail-convert-to-memo",
1289 "stock_insert-note",
1290 N_("Create a Mem_o"),
1291 NULL,
1292 N_("Create a new memo from the selected message"),
1293 G_CALLBACK (action_mail_convert_to_memo_cb) },
1294
1295 { "mail-convert-to-task",
1296 "stock_todo",
1297 N_("Create a _Task"),
1298 NULL,
1299 N_("Create a new task from the selected message"),
1300 G_CALLBACK (action_mail_convert_to_task_cb) }
1301 };
1302
1303 static GtkActionEntry single_selection_entries[] = {
1304
1305 { "mail-convert-to-meeting",
1306 "stock_new-meeting",
1307 N_("Create a _Meeting"),
1308 NULL,
1309 N_("Create a new meeting from the selected message"),
1310 G_CALLBACK (action_mail_convert_to_meeting_cb) }
1311 };
1312
1313 static void
1314 update_actions_any_cb (EMailReader *reader,
1315 guint32 state,
1316 GtkActionGroup *action_group)
1317 {
1318 gboolean sensitive;
1319
1320 sensitive =
1321 (state & E_MAIL_READER_SELECTION_SINGLE) ||
1322 (state & E_MAIL_READER_SELECTION_MULTIPLE);
1323
1324 gtk_action_group_set_sensitive (action_group, sensitive);
1325 }
1326
1327 static void
1328 update_actions_one_cb (EMailReader *reader,
1329 guint32 state,
1330 GtkActionGroup *action_group)
1331 {
1332 gboolean sensitive;
1333
1334 sensitive = (state & E_MAIL_READER_SELECTION_SINGLE);
1335
1336 gtk_action_group_set_sensitive (action_group, sensitive);
1337 }
1338
1339 static void
1340 setup_actions (EMailReader *reader,
1341 GtkUIManager *ui_manager)
1342 {
1343 GtkActionGroup *action_group;
1344 const gchar *domain = GETTEXT_PACKAGE;
1345
1346 action_group = gtk_action_group_new ("mail-convert-any");
1347 gtk_action_group_set_translation_domain (action_group, domain);
1348 gtk_action_group_add_actions (
1349 action_group, multi_selection_entries,
1350 G_N_ELEMENTS (multi_selection_entries), reader);
1351 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
1352 g_object_unref (action_group);
1353
1354 /* GtkUIManager now owns the action group reference.
1355 * The signal we're connecting to will only be emitted
1356 * during the GtkUIManager's lifetime, so the action
1357 * group will not disappear on us. */
1358
1359 g_signal_connect (
1360 reader, "update-actions",
1361 G_CALLBACK (update_actions_any_cb), action_group);
1362
1363 action_group = gtk_action_group_new ("mail-convert-one");
1364 gtk_action_group_set_translation_domain (action_group, domain);
1365 gtk_action_group_add_actions (
1366 action_group, single_selection_entries,
1367 G_N_ELEMENTS (single_selection_entries), reader);
1368 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
1369 g_object_unref (action_group);
1370
1371 /* GtkUIManager now owns the action group reference.
1372 * The signal we're connecting to will only be emitted
1373 * during the GtkUIManager's lifetime, so the action
1374 * group will not disappear on us. */
1375
1376 g_signal_connect (
1377 reader, "update-actions",
1378 G_CALLBACK (update_actions_one_cb), action_group);
1379 }
1380
1381 gboolean
1382 mail_browser_init (GtkUIManager *ui_manager,
1383 EMailBrowser *browser)
1384 {
1385 setup_actions (E_MAIL_READER (browser), ui_manager);
1386
1387 return TRUE;
1388 }
1389
1390 gboolean
1391 mail_shell_view_init (GtkUIManager *ui_manager,
1392 EShellView *shell_view)
1393 {
1394 EShellContent *shell_content;
1395
1396 shell_content = e_shell_view_get_shell_content (shell_view);
1397
1398 setup_actions (E_MAIL_READER (shell_content), ui_manager);
1399
1400 return TRUE;
1401 }