aboutsummaryrefslogblamecommitdiffstats
path: root/plugins/mail-to-task/mail-to-task.c
blob: 89ba097611f0540eb4699785fdb1378b17366563 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                                    
                                                                             








                                                        
 
                                        

                    
                   

      
                  

                           
 

                                              
 
                                         
 
                                  
 
                                

                                    

                                         


                                

                              

                                              
                                             
                                             









                                                                   


                                                                          
                                                                         
 
                   








                                          
                                  




                                                               

                                                










                                                             
                                                                    













































                                                                                                  
           


                                         
 
                                                  
                                  
                                                                   
                       
 




                                         


                                                                                    
 




                                                              
 

                                                             
                                                 
 
                                                                                   
                                            
 






                                                                                             
                                                                       
 
                                                 
                                              

                                                                       



                                                                   





                                                                            
 

                                                                          



                                                                           
 

                                                            
                                                          
                                                        
 
                               


                                 
                    

                                        


                                              
                                          
























                                                                                                                     
           

                                           

                                  
                            

                                                             
                                       
                               
                          
                                        

                                        
 
                                                                     


                       


                                                            
                                                           
                                                                                    
                                                                              


                        


                       



                                                            

                                                                   
                                                                               
                                                                      
                                
 
                                     
                                                                                     
                                                           



                                                                                    
                                             
                        
                                                                   
            


                                                           
 
                                                        

                     

                                     
                                            

 
              

                                   
 





                                       
                                                                    



                                                
 
                             
                                  
 
                                                               
                                                                                

         

                                                                                
 
                                                    
 

                                                                    
 

                                                                 
 









                                                                 

 




                           
           
                                                  


                                               
                                                    


                                                                 
                                                             
 
                                






                                                  
                                                    
                     
                             
 




                                                                      
 
                               
 
                                


           


                                           
 



                                                                             
                                  

                                  

                                      

                                     
                                     
                         
                                          
 

                                     
 
                                                                     


                                                      

                                                                         

                       
                                                  
                                            
 






                                                                       





                                                                      
 
                                          
                                        
                                         
 


                                                                     
 


                                                                              
                                    
 

                                                             
                                                    
 
                                                                


                                                                  


                                                                       
 

                                    

                                       



                                                                          
 

                                                    
 




                                                            
 
                                                     


                                                                        

         

                                   

                                                       
                                                             
 
                                              

                                     

 
           

                                  













                                                                             

                    

                      

  
               












                                                                            

                                       






                                                                    
                                                        

 

                   
                           

                                                                   





                              










                                                     






                                          



                    

                                    


                                                         
                                 



















                                                                           
                                                        



                                
                                             

                                                                                                                
                                            

                                                                                                               
                                            










                                                                                                               
                                                             
                                       



                                
                                             



                                                                                                                            
                      
                                            



                                                                                                                           
                      
                                            



                                                                                                                           








                                        

                                       

                                            
 

                       
 

                                                                                                                                  
 
                                                                               
                                                      
                                 

 







                                                           

                                                   
 




                                                           
 

                       
 


                                                                              
 


                                       
 



                                                                               
 
                                                                             

                                                                           


                                          
 
                                                 
 


                           
 





                                                                         
 
                                         
 
                                                                
 

                                                           
 


                                                                                           
 

                                                                       
 


                                                                                                                                                                          
 
                                                    
 







                                                                                                                                
 
                                 


                                                                                               
                                                     


                                                                              
                         
                                     

                 

                                     
 


                                   
 



                                                                            
 


                                                                                  
 





                                                                            
 
                                                                 
 




                                                                                                                  
                 



                                                                                                                      



                                                                                                                 
                                       

         


                     
                
                           
                            
                        
                             




                                  
 
                                          
                                           
                                     
                           

                                  
                                                                         
                                                                                                            
                                                              
                        

                                                                                      

                                                                        

                                                                                                                                                          
                                                            

                                                                                                                                                         
                                                            






                                                                                                                                                         
                
                       
                                                                                         

                                              
                                                  
 













                                                                                                         





                                                                                                                        
                                                   

                                 
 
                                                              


                                                  

                                                
                                                

                                                                       
                                                                         
                                                                 

                                                                    
                                       
                                         
                         

                                                      

                                              
                                                             

                                                                                            
                                                            

                                                                                           
                                                            






                                                                                              
                                                                                                    
                                                                
 
                                                                             




                                                                       



                                                                              
                                            










                                                                                 

                                                   
                                                 

                                                                                                           
                                                                               


                                                                         
 


                                                                


                                                                       


                                                                                             
                                                                            
 


                                                                                        
 


                                                             




















                                                                                                 
 
                                                                                                                                           

                                                       


                                                                           

                                              

                                                 
                 
 





                                                                
                 
         
 
                         
                                      
                                  
                                
                                     


                      


                                   
                    

 
               

                                               
 
                       

                       
                              


















                                                    
              
                                       
 
                              
                           

                 
                                                          
 
                                                                   

                            
                                                                       

                                                                       
                              
                            




                    
           
                                                
                                       
                                   
 


                                  
                            
                        




                                    

                             
                                                   
                                                   
                                                        
 
                                                               
                             








                                                                                        
                                                  


                                                

         


                                                                      
 















                                                                                            
 
                                                                         
 









                                                                               


                         
 


                                                                 
                                  
                                          
 
                                                              

                                                          
 
                                                                  
                                                           
 


                                                         
                                                                            

                                                                                  

                                            

                                               
                             

                                                                                            
                                          


                                             
                          

         
                     
                                                                                     
                                          

                                       
                                     
 
                                                                        
                              




                                                                    
                 
 



                                                   
                                  
                                                      
 
                                   
                                                                         


                                                   
                                                                                               


                                                                    
                 

         

                                        

 

                                                   
                                                     
 
                                                                       

 

                                                     
                                                       
 
                                                                      
 
 

                                                  
                                                    
 
                                                                      
 
 

                                                  
                                                    
 
                                                                      

 




                                                                         


                                  
                                 
               
                                                             

                                                         

                                 
                               
               
                                                            



                                                        
                               
               
                                                            

                                                       
 
                                                    
 


                                    
               

                                                               


           
                                           
                                     
                                                    
 
                           




                                                           

                                                                 
 

                                           
                                     


                                                    


                                                             
                                                                 
 
 


                                        
 
                                     








                                                                         
 



                                                              
 





                                                                       
                                      








                                                                         

                          





















                                                                    

                    
 
/*
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *      Michael Zucchi <notzed@novell.com>
 *      Rodrigo Moya <rodrigo@novell.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

/* Convert a mail message into a task */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <glib/gi18n-lib.h>

#include <libecal/libecal.h>
#include <libedataserverui/libedataserverui.h>

#include <libemail-engine/e-mail-utils.h>

#include <e-util/e-dialog-utils.h>

#include <misc/e-popup-action.h>
#include <misc/e-attachment-store.h>

#include <shell/e-shell-view.h>
#include <shell/e-shell-window-actions.h>

#include <mail/e-mail-browser.h>
#include <mail/em-utils.h>
#include <mail/message-list.h>

#include <calendar/gui/dialogs/comp-editor.h>
#include <calendar/gui/dialogs/event-editor.h>
#include <calendar/gui/dialogs/memo-editor.h>
#include <calendar/gui/dialogs/task-editor.h>

#define E_SHELL_WINDOW_ACTION_CONVERT_TO_EVENT(window) \
    E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-event")
#define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING(window) \
    E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-meeting")
#define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO(window) \
    E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-memo")
#define E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK(window) \
    E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-task")

gboolean    mail_browser_init       (GtkUIManager *ui_manager,
                         EMailBrowser *browser);
gboolean    mail_shell_view_init        (GtkUIManager *ui_manager,
                         EShellView *shell_view);

static CompEditor *
get_component_editor (EShell *shell,
                      ECalClient *client,
                      ECalComponent *comp,
                      gboolean is_new,
                      GError **error)
{
    ECalComponentId *id;
    CompEditorFlags flags = 0;
    CompEditor *editor = NULL;
    ESourceRegistry *registry;

    g_return_val_if_fail (E_IS_SHELL (shell), NULL);
    g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
    g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);

    registry = e_shell_get_registry (shell);

    id = e_cal_component_get_id (comp);
    g_return_val_if_fail (id != NULL, NULL);
    g_return_val_if_fail (id->uid != NULL, NULL);

    if (is_new) {
        flags |= COMP_EDITOR_NEW_ITEM;
    } else {
        editor = comp_editor_find_instance (id->uid);
    }

    if (!editor) {
        if (itip_organizer_is_user (registry, comp, client))
            flags |= COMP_EDITOR_USER_ORG;

        switch (e_cal_component_get_vtype (comp)) {
        case E_CAL_COMPONENT_EVENT:
            if (e_cal_component_has_attendees (comp))
                flags |= COMP_EDITOR_MEETING;

            editor = event_editor_new (client, shell, flags);

            if (flags & COMP_EDITOR_MEETING)
                event_editor_show_meeting (EVENT_EDITOR (editor));
            break;
        case E_CAL_COMPONENT_TODO:
            if (e_cal_component_has_attendees (comp))
                flags |= COMP_EDITOR_IS_ASSIGNED;

            editor = task_editor_new (client, shell, flags);

            if (flags & COMP_EDITOR_IS_ASSIGNED)
                task_editor_show_assignment (TASK_EDITOR (editor));
            break;
        case E_CAL_COMPONENT_JOURNAL:
            if (e_cal_component_has_organizer (comp))
                flags |= COMP_EDITOR_IS_SHARED;

            editor = memo_editor_new (client, shell, flags);
            break;
        default:
            if (error)
                *error = e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL);
            break;
        }

        if (editor) {
            comp_editor_edit_comp (editor, comp);

            /* request save for new events */
            comp_editor_set_changed (editor, is_new);
        }
    }

    e_cal_component_free_id (id);

    return editor;
}

static void
set_attendees (ECalComponent *comp,
               CamelMimeMessage *message,
               const gchar *organizer)
{
    GSList *attendees = NULL, *to_free = NULL;
    ECalComponentAttendee *ca;
    CamelInternetAddress *from = NULL, *to, *cc, *bcc, *arr[4];
    gint len, i, j;

    if (message->reply_to)
        from = message->reply_to;
    else if (message->from)
        from = message->from;

    to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
    cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
    bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);

    arr[0] = from; arr[1] = to; arr[2] = cc; arr[3] = bcc;

    for (j = 0; j < 4; j++) {
        if (!arr[j])
            continue;

        len = CAMEL_ADDRESS (arr[j])->addresses->len;
        for (i = 0; i < len; i++) {
            const gchar *name, *addr;

            if (camel_internet_address_get (arr[j], i, &name, &addr)) {
                gchar *temp;

                temp = g_strconcat ("mailto:", addr, NULL);
                if (organizer && g_ascii_strcasecmp (temp, organizer) == 0) {
                    /* do not add organizer twice */
                    g_free (temp);
                    continue;
                }

                ca = g_new0 (ECalComponentAttendee, 1);

                ca->value = temp;
                ca->cn = name;
                ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
                ca->status = ICAL_PARTSTAT_NEEDSACTION;
                if (j == 0) {
                    /* From */
                    ca->role = ICAL_ROLE_CHAIR;
                } else if (j == 2) {
                    /* BCC  */
                    ca->role = ICAL_ROLE_OPTPARTICIPANT;
                } else {
                    /* all other */
                    ca->role = ICAL_ROLE_REQPARTICIPANT;
                }

                to_free = g_slist_prepend (to_free, temp);

                attendees = g_slist_append (attendees, ca);
            }
        }
    }

    e_cal_component_set_attendee_list (comp, attendees);

    g_slist_foreach (attendees, (GFunc) g_free, NULL);
    g_slist_foreach (to_free, (GFunc) g_free, NULL);

    g_slist_free (to_free);
    g_slist_free (attendees);
}

static const gchar *
prepend_from (CamelMimeMessage *message,
              gchar **text)
{
    gchar *res, *tmp, *addr = NULL;
    const gchar *name = NULL, *eml = NULL;
    CamelInternetAddress *from = NULL;

    g_return_val_if_fail (message != NULL, NULL);
    g_return_val_if_fail (text != NULL, NULL);

    if (message->reply_to)
        from = message->reply_to;
    else if (message->from)
        from = message->from;

    if (from && camel_internet_address_get (from, 0, &name, &eml))
        addr = camel_internet_address_format_address (name, eml);

    /* To Translators: The full sentence looks like: "Created from a mail by John Doe <john.doe@myco.example>" */
    tmp = g_strdup_printf (_("Created from a mail by %s"), addr ? addr : "");

    res = g_strconcat (tmp, "\n", *text, NULL);

    g_free (tmp);
    g_free (*text);

    *text = res;

    return res;
}

static void
set_description (ECalComponent *comp,
                 CamelMimeMessage *message)
{
    CamelDataWrapper *content;
    CamelStream *stream;
    CamelContentType *type;
    CamelMimePart *mime_part = CAMEL_MIME_PART (message);
    ECalComponentText *text = NULL;
    GByteArray *byte_array;
    GSList *sl = NULL;
    gchar *str, *convert_str = NULL;
    gsize bytes_read, bytes_written;
    gint count = 2;

    content = camel_medium_get_content ((CamelMedium *) message);
    if (!content)
        return;

    /*
     * Get non-multipart content from multipart message.
     */
    while (CAMEL_IS_MULTIPART (content) && count > 0) {
        mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), 0);
        content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
        count--;
    }

    if (!mime_part)
        return;

    type = camel_mime_part_get_content_type (mime_part);
    if (!camel_content_type_is (type, "text", "plain"))
        return;

    byte_array = g_byte_array_new ();
    stream = camel_stream_mem_new_with_byte_array (byte_array);
    camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL);
    str = g_strndup ((gchar *) byte_array->data, byte_array->len);
    g_object_unref (stream);

    /* convert to UTF-8 string */
    if (str && content->mime_type->params && content->mime_type->params->value) {
        convert_str = g_convert (str, strlen (str),
                     "UTF-8", content->mime_type->params->value,
                     &bytes_read, &bytes_written, NULL);
    }

    text = g_new0 (ECalComponentText, 1);
    if (convert_str)
        text->value = prepend_from (message, &convert_str);
    else
        text->value = prepend_from (message, &str);
    text->altrep = NULL;
    sl = g_slist_append (sl, text);

    e_cal_component_set_description_list (comp, sl);

    g_free (str);
    if (convert_str)
        g_free (convert_str);
    e_cal_component_free_text_list (sl);
}

static gchar *
set_organizer (ECalComponent *comp,
               CamelFolder *folder)
{
    EShell *shell;
    ESource *source = NULL;
    ESourceRegistry *registry;
    ESourceMailIdentity *extension;
    const gchar *extension_name;
    const gchar *address, *name;
    ECalComponentOrganizer organizer = {NULL, NULL, NULL, NULL};
    gchar *mailto = NULL;

    shell = e_shell_get_default ();
    registry = e_shell_get_registry (shell);

    if (folder != NULL) {
        CamelStore *store;

        store = camel_folder_get_parent_store (folder);
        source = em_utils_ref_mail_identity_for_store (registry, store);
    }

    if (source == NULL)
        source = e_source_registry_ref_default_mail_identity (registry);

    g_return_val_if_fail (source != NULL, NULL);

    extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
    extension = e_source_get_extension (source, extension_name);

    name = e_source_mail_identity_get_name (extension);
    address = e_source_mail_identity_get_address (extension);

    if (name != NULL && address != NULL) {
        mailto = g_strconcat ("mailto:", address, NULL);
        organizer.value = mailto;
        organizer.cn = name;
        e_cal_component_set_organizer (comp, &organizer);
    }

    g_object_unref (source);

    return mailto;
}

struct _att_async_cb_data {
    gchar **uris;
    EFlag *flag;
};

static void
attachment_load_finished (EAttachmentStore *store,
                          GAsyncResult *result,
                          gpointer user_data)
{
    struct _att_async_cb_data *data = user_data;

    /* XXX Should be no need to check for error here.
     *     This is just to reset state in the EAttachment. */
    e_attachment_store_load_finish (store, result, NULL);

    e_flag_set (data->flag);
}

static void
attachment_save_finished (EAttachmentStore *store,
                          GAsyncResult *result,
                          gpointer user_data)
{
    struct _att_async_cb_data *data = user_data;
    gchar **uris;
    GError *error = NULL;

    uris = e_attachment_store_save_finish (store, result, &error);
    if (error)
        data->uris = NULL;
    else
        data->uris = uris;

    g_clear_error (&error);

    e_flag_set (data->flag);
}

static void
set_attachments (ECalClient *client,
                 ECalComponent *comp,
                 CamelMimeMessage *message)
{
    /* XXX Much of this is copied from CompEditor::get_attachment_list().
     *     Perhaps it should be split off as a separate utility? */

    EAttachmentStore *store;
    CamelDataWrapper *content;
    CamelMultipart *multipart;
    GFile *destination;
    GList *attachment_list = NULL;
    GSList *uri_list = NULL;
    const gchar *comp_uid = NULL;
    const gchar *local_store;
    gchar *filename_prefix, *tmp;
    gint ii, n_parts;
    struct _att_async_cb_data cb_data;

    cb_data.flag = e_flag_new ();
    cb_data.uris = NULL;

    content = camel_medium_get_content ((CamelMedium *) message);
    if (!content || !CAMEL_IS_MULTIPART (content))
        return;

    n_parts = camel_multipart_get_number (CAMEL_MULTIPART (content));
    if (n_parts < 1)
        return;

    e_cal_component_get_uid (comp, &comp_uid);
    g_return_if_fail (comp_uid != NULL);

    tmp = g_strdup (comp_uid);
    e_filename_make_safe (tmp);
    filename_prefix = g_strconcat (tmp, "-", NULL);
    g_free (tmp);

    local_store = e_cal_client_get_local_attachment_store (client);
    destination = g_file_new_for_path (local_store);

    /* Create EAttachments from the MIME parts and add them to the
     * attachment store. */

    multipart = CAMEL_MULTIPART (content);
    store = E_ATTACHMENT_STORE (e_attachment_store_new ());

    for (ii = 1; ii < n_parts; ii++) {
        EAttachment *attachment;
        CamelMimePart *mime_part;

        attachment = e_attachment_new ();
        mime_part = camel_multipart_get_part (multipart, ii);
        e_attachment_set_mime_part (attachment, mime_part);

        attachment_list = g_list_append (attachment_list, attachment);
    }

    e_flag_clear (cb_data.flag);

    e_attachment_store_load_async (
        store, attachment_list, (GAsyncReadyCallback)
        attachment_load_finished, &cb_data);

    /* Loading should be instantaneous since we already have
     * the full content, but we need to wait for the callback.
     */
    e_flag_wait (cb_data.flag);

    g_list_foreach (attachment_list, (GFunc) g_object_unref, NULL);
    g_list_free (attachment_list);

    cb_data.uris = NULL;
    e_flag_clear (cb_data.flag);

    e_attachment_store_save_async (
        store, destination, filename_prefix,
        (GAsyncReadyCallback) attachment_save_finished, &cb_data);

    g_free (filename_prefix);

    /* We can't return until we have results. */
    e_flag_wait (cb_data.flag);

    if (cb_data.uris == NULL) {
        e_flag_free (cb_data.flag);
        g_warning ("No attachment URIs retrieved.");
        return;
    }

    /* Transfer the URI strings to the GSList. */
    for (ii = 0; cb_data.uris[ii] != NULL; ii++) {
        uri_list = g_slist_prepend (uri_list, cb_data.uris[ii]);
        cb_data.uris[ii] = NULL;
    }

    e_flag_free (cb_data.flag);
    g_free (cb_data.uris);

    /* XXX Does this take ownership of the list? */
    e_cal_component_set_attachment_list (comp, uri_list);

    e_attachment_store_remove_all (store);
    g_object_unref (destination);
    g_object_unref (store);
}

static void
set_priority (ECalComponent *comp,
              CamelMimePart *part)
{
    const gchar *prio;

    g_return_if_fail (comp != NULL);
    g_return_if_fail (part != NULL);

    prio = camel_header_raw_find (& (part->headers), "X-Priority", NULL);
    if (prio && atoi (prio) > 0) {
        gint priority = 1;

        e_cal_component_set_priority (comp, &priority);
    }
}

struct _report_error
{
    gchar *format;
    gchar *param;
};

static gboolean
do_report_error (struct _report_error *err)
{
    if (err) {
        e_notice (NULL, GTK_MESSAGE_ERROR, err->format, err->param);
        g_free (err->format);
        g_free (err->param);
        g_free (err);
    }

    return FALSE;
}

static void
report_error_idle (const gchar *format,
                   const gchar *param)
{
    struct _report_error *err = g_new (struct _report_error, 1);

    err->format = g_strdup (format);
    err->param = g_strdup (param);

    g_usleep (250);
    g_idle_add ((GSourceFunc) do_report_error, err);
}

struct _manage_comp
{
    ECalClient *client;
    ECalComponent *comp;
    icalcomponent *stored_comp; /* the one in client already */
    GCond *cond;
    GMutex *mutex;
    gint mails_count;
    gint mails_done;
    gchar *editor_title;
    gboolean can_continue;
};

static void
free_manage_comp_struct (struct _manage_comp *mc)
{
    g_return_if_fail (mc != NULL);

    g_object_unref (mc->comp);
    g_object_unref (mc->client);
    if (mc->stored_comp)
        icalcomponent_free (mc->stored_comp);
    if (mc->mutex)
        g_mutex_free (mc->mutex);
    if (mc->cond)
        g_cond_free (mc->cond);
    if (mc->editor_title)
        g_free (mc->editor_title);

    g_free (mc);
}

static gint
do_ask (const gchar *text,
        gboolean is_create_edit_add)
{
    gint res;
    GtkWidget *dialog = gtk_message_dialog_new (NULL,
        GTK_DIALOG_MODAL,
        GTK_MESSAGE_QUESTION,
        is_create_edit_add ? GTK_BUTTONS_NONE : GTK_BUTTONS_YES_NO,
        "%s", text);

    if (is_create_edit_add) {
        gtk_dialog_add_buttons (GTK_DIALOG (dialog),
            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
            GTK_STOCK_EDIT, GTK_RESPONSE_YES,
            GTK_STOCK_NEW, GTK_RESPONSE_NO,
            NULL);
    }

    res = gtk_dialog_run (GTK_DIALOG (dialog));

    gtk_widget_destroy (dialog);

    return res;
}

static const gchar *
get_question_edit_old (ECalClientSourceType source_type)
{
    const gchar *ask = NULL;

    switch (source_type) {
    case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
        ask = _("Selected calendar contains event '%s' already. Would you like to edit the old event?");
        break;
    case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
        ask = _("Selected task list contains task '%s' already. Would you like to edit the old task?");
        break;
    case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
        ask = _("Selected memo list contains memo '%s' already. Would you like to edit the old memo?");
        break;
    default:
        g_assert_not_reached ();
        break;
    }

    return ask;
}

static const gchar *
get_question_add_all_mails (ECalClientSourceType source_type,
                            gint count)
{
    const gchar *ask = NULL;

    switch (source_type) {
    case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
        /* Translators: Note there are always more than 10 mails selected */
        ask = ngettext ("You have selected %d mails to be converted to events. Do you really want to add them all?",
                "You have selected %d mails to be converted to events. Do you really want to add them all?",
                count);
        break;
    case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
        /* Translators: Note there are always more than 10 mails selected */
        ask = ngettext ("You have selected %d mails to be converted to tasks. Do you really want to add them all?",
                "You have selected %d mails to be converted to tasks. Do you really want to add them all?",
                count);
        break;
    case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
        /* Translators: Note there are always more than 10 mails selected */
        ask = ngettext ("You have selected %d mails to be converted to memos. Do you really want to add them all?",
                "You have selected %d mails to be converted to memos. Do you really want to add them all?",
                count);
        break;
    default:
        g_assert_not_reached ();
        break;
    }

    return ask;
}

static void
comp_editor_closed (CompEditor *editor,
                    gboolean accepted,
                    struct _manage_comp *mc)
{
    if (!mc)
        return;

    if (!accepted && mc->mails_done < mc->mails_count)
        mc->can_continue = (do_ask (_("Do you wish to continue converting remaining mails?"), FALSE) == GTK_RESPONSE_YES);

    /* Signal the do_mail_to_event thread that editor was closed and editor
     * for next event can be displayed (if any) */
    g_cond_signal (mc->cond);
}

/*
 * This handler takes title of the editor window and
 * inserts information about number of processed mails and
 * number of all mails to process, so the window title
 * will look like "Appointment (3/10) - An appoitment name"
 */
static void
comp_editor_title_changed (GtkWidget *widget,
                           GParamSpec *pspec,
                           struct _manage_comp *mc)
{
    GtkWindow *editor = GTK_WINDOW (widget);
    const gchar *title = gtk_window_get_title (editor);
    gchar *new_title;
    gchar *splitter;
    gchar *comp_name, *task_name;

    if (!mc)
        return;

    /* Recursion prevence */
    if (mc->editor_title && g_utf8_collate (mc->editor_title, title) == 0)
        return;

    splitter = strchr (title, '-');
    if (!splitter)
        return;

    comp_name = g_strndup (title, splitter - title - 1);
    task_name = g_strdup (splitter + 2);
    new_title = g_strdup_printf ("%s (%d/%d) - %s",
            comp_name, mc->mails_done, mc->mails_count, task_name);

    /* Remember the new title, so that when gtk_window_set_title() causes
     * this handler to be recursively called, we can recognize that and
     * prevent endless recursion */
    if (mc->editor_title)
        g_free (mc->editor_title);
    mc->editor_title = new_title;

    gtk_window_set_title (editor, new_title);

    g_free (comp_name);
    g_free (task_name);
}

static gboolean
do_manage_comp_idle (struct _manage_comp *mc)
{
    GError *error = NULL;
    ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST;
    ECalComponent *edit_comp = NULL;

    g_return_val_if_fail (mc, FALSE);

    source_type = e_cal_client_get_source_type (mc->client);

    if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) {
        free_manage_comp_struct (mc);

        g_warning ("mail-to-task: Incorrect call of %s, no data given", G_STRFUNC);
        return FALSE;
    }

    if (mc->stored_comp) {
        const gchar *ask = get_question_edit_old (source_type);

        if (ask) {
            gchar *msg = g_strdup_printf (ask, icalcomponent_get_summary (mc->stored_comp) ? icalcomponent_get_summary (mc->stored_comp) : _("[No Summary]"));
            gint chosen;

            chosen = do_ask (msg, TRUE);

            if (chosen == GTK_RESPONSE_YES) {
                edit_comp = e_cal_component_new ();
                if (!e_cal_component_set_icalcomponent (edit_comp, icalcomponent_new_clone (mc->stored_comp))) {
                    g_object_unref (edit_comp);
                    edit_comp = NULL;
                    error = g_error_new (E_CAL_CLIENT_ERROR,
                        E_CAL_CLIENT_ERROR_INVALID_OBJECT,
                        "%s", _("Invalid object returned from a server"));

                }
            } else if (chosen == GTK_RESPONSE_NO) {
                /* user wants to create a new event, thus generate a new UID */
                gchar *new_uid = e_cal_component_gen_uid ();
                edit_comp = mc->comp;
                e_cal_component_set_uid (edit_comp, new_uid);
                e_cal_component_set_recurid (edit_comp, NULL);
                g_free (new_uid);
            }
            g_free (msg);
        }
    } else {
        edit_comp = mc->comp;
    }

    if (edit_comp) {
        EShell *shell;
        CompEditor *editor;

        /* FIXME Pass in the EShell instance. */
        shell = e_shell_get_default ();
        editor = get_component_editor (shell, mc->client, edit_comp,
                edit_comp == mc->comp, &error);

        if (editor && !error) {
            /* Force editor's title change */
            comp_editor_title_changed (GTK_WIDGET (editor), NULL, mc);

            g_signal_connect (
                editor, "notify::title",
                G_CALLBACK (comp_editor_title_changed), mc);
            g_signal_connect (
                editor, "comp_closed",
                G_CALLBACK (comp_editor_closed), mc);

            gtk_window_present (GTK_WINDOW (editor));

            if (edit_comp != mc->comp)
                g_object_unref (edit_comp);
        } else {
            g_warning ("Failed to create event editor: %s", error ? error->message : "Unknown error");
            g_cond_signal (mc->cond);
        }
    } else {
        /* User canceled editing already existing event, so treat it as if he just closed the editor window */
        comp_editor_closed (NULL, FALSE, mc);
        g_cond_signal (mc->cond);
    }

    if (error) {
        e_notice (NULL, GTK_MESSAGE_ERROR, _("An error occurred during processing: %s"), error->message);
        g_clear_error (&error);
    }

    return FALSE;
}

typedef struct {
    ECalClient *client;
    CamelFolder *folder;
    GPtrArray *uids;
    gchar *selected_text;
    gboolean with_attendees;
}AsyncData;

static gboolean
do_mail_to_event (AsyncData *data)
{
    ECalClient *client = data->client;
    CamelFolder *folder = data->folder;
    GPtrArray *uids = data->uids;
    GError *err = NULL;

    /* open the task client */
    if (!e_client_open_sync (E_CLIENT (client), FALSE, NULL, &err)) {
        report_error_idle (_("Cannot open calendar. %s"), err ? err->message : _("Unknown error."));
    } else if (e_client_is_readonly (E_CLIENT (client))) {
        if (err)
            report_error_idle ("Check readonly failed. %s", err->message);
        else {
            switch (e_cal_client_get_source_type (client)) {
            case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
                report_error_idle (_("Selected source is read only, thus cannot create event there. Select other source, please."), NULL);
                break;
            case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
                report_error_idle (_("Selected source is read only, thus cannot create task there. Select other source, please."), NULL);
                break;
            case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
                report_error_idle (_("Selected source is read only, thus cannot create memo there. Select other source, please."), NULL);
                break;
            default:
                g_assert_not_reached ();
                break;
            }
        }
    } else {
        gint i;
        ECalClientSourceType source_type = e_cal_client_get_source_type (client);
        ECalComponentDateTime dt, dt2;
        struct icaltimetype tt, tt2;
        struct _manage_comp *oldmc = NULL;

        #define cache_backend_prop(prop) {                          \
            gchar *val = NULL;                              \
            e_client_get_backend_property_sync (E_CLIENT (client), prop, &val, NULL, NULL); \
            g_free (val);                                   \
        }

        /* precache backend properties, thus editor have them ready when needed */
        cache_backend_prop (CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS);
        cache_backend_prop (CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS);
        cache_backend_prop (CAL_BACKEND_PROPERTY_DEFAULT_OBJECT);
        e_client_get_capabilities (E_CLIENT (client));

        #undef cache_backend_prop

        /* set start day of the event as today, without time - easier than looking for a calendar's time zone */
        tt = icaltime_today ();
        dt.value = &tt;
        dt.tzid = NULL;

        tt2 = tt;
        icaltime_adjust (&tt2, 1, 0, 0, 0);
        dt2.value = &tt2;
        dt2.tzid = NULL;

        for (i = 0; i < (uids ? uids->len : 0); i++) {
            CamelMimeMessage *message;
            ECalComponent *comp;
            ECalComponentText text;
            icalproperty *icalprop;
            icalcomponent *icalcomp;
            struct _manage_comp *mc;

            /* retrieve the message from the CamelFolder */
            /* FIXME Not passing a GCancellable or GError. */
            message = camel_folder_get_message_sync (
                folder, g_ptr_array_index (uids, i),
                NULL, NULL);
            if (!message) {
                continue;
            }

            comp = e_cal_component_new ();

            switch (source_type) {
            case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
                e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
                break;
            case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
                e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
                break;
            case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
                e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
                break;
            default:
                g_assert_not_reached ();
                break;
            }

            e_cal_component_set_uid (comp, camel_mime_message_get_message_id (message));
            e_cal_component_set_dtstart (comp, &dt);

            if (source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) {
                /* make it an all-day event */
                e_cal_component_set_dtend (comp, &dt2);
            }

            /* set the summary */
            text.value = camel_mime_message_get_subject (message);
            text.altrep = NULL;
            e_cal_component_set_summary (comp, &text);

            /* set all fields */
            if (data->selected_text) {
                GSList sl;

                text.value = data->selected_text;
                text.altrep = NULL;
                sl.next = NULL;
                sl.data = &text;

                e_cal_component_set_description_list (comp, &sl);
            } else
                set_description (comp, message);

            if (data->with_attendees) {
                gchar *organizer;

                /* set actual user as organizer, to be able to change event's properties */
                organizer = set_organizer (comp, data->folder);
                set_attendees (comp, message, organizer);
                g_free (organizer);
            }

            /* set attachment files */
            set_attachments (client, comp, message);

            /* priority */
            set_priority (comp, CAMEL_MIME_PART (message));

            /* no need to increment a sequence number, this is a new component */
            e_cal_component_abort_sequence (comp);

            icalcomp = e_cal_component_get_icalcomponent (comp);

            icalprop = icalproperty_new_x ("1");
            icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR");
            icalcomponent_add_property (icalcomp, icalprop);

            mc = g_new0 (struct _manage_comp, 1);
            mc->client = g_object_ref (client);
            mc->comp = g_object_ref (comp);
            mc->mutex = g_mutex_new ();
            mc->cond = g_cond_new ();
            mc->mails_count = uids->len;
            mc->mails_done = i + 1; /* Current task */
            mc->editor_title = NULL;
            mc->can_continue = TRUE;

            if (oldmc) {
                /* Wait for user to quit the editor created in previous iteration
                 * before displaying next one */
                gboolean can_continue;
                g_mutex_lock (oldmc->mutex);
                g_cond_wait (oldmc->cond, oldmc->mutex);
                g_mutex_unlock (oldmc->mutex);
                can_continue = oldmc->can_continue;
                free_manage_comp_struct (oldmc);
                oldmc = NULL;

                if (!can_continue)
                    break;
            }

            if (!e_cal_client_get_object_sync (client, icalcomponent_get_uid (icalcomp), NULL, &(mc->stored_comp), NULL, NULL))
                mc->stored_comp = NULL;

            g_idle_add ((GSourceFunc) do_manage_comp_idle, mc);

            oldmc = mc;

            g_object_unref (comp);
            g_object_unref (message);

        }

        /* Wait for the last editor and then clean up */
        if (oldmc) {
            g_mutex_lock (oldmc->mutex);
            g_cond_wait (oldmc->cond, oldmc->mutex);
            g_mutex_unlock (oldmc->mutex);
            free_manage_comp_struct (oldmc);
        }
    }

    /* free memory */
    g_object_unref (data->client);
    em_utils_uids_free (uids);
    g_object_unref (folder);
    g_free (data->selected_text);
    g_free (data);
    data = NULL;

    if (err)
        g_error_free (err);

    return TRUE;
}

static gboolean
text_contains_nonwhitespace (const gchar *text,
                             gint len)
{
    const gchar *p;
    gunichar c = 0;

    if (!text || len <= 0)
        return FALSE;

    p = text;

    while (p && p - text < len) {
        c = g_utf8_get_char (p);
        if (!c)
            break;

        if (!g_unichar_isspace (c))
            break;

        p = g_utf8_next_char (p);
    }

    return p - text < len - 1 && c != 0;
}

/* should be freed with g_free after done with it */
static gchar *
get_selected_text (EMailReader *reader)
{
    EMailDisplay *display;
    gchar *text = NULL;
    gint len;

    display = e_mail_reader_get_mail_display (reader);

    if (!e_web_view_is_selection_active (E_WEB_VIEW (display)))
        return NULL;

    text = e_mail_display_get_selection_plain_text (display, &len);

    if (text == NULL || !text_contains_nonwhitespace (text, len)) {
        g_free (text);
        return NULL;
    }

    return text;
}

static void
mail_to_event (ECalClientSourceType source_type,
               gboolean with_attendees,
               EMailReader *reader)
{
    EShell *shell;
    EMailBackend *backend;
    ESourceRegistry *registry;
    CamelFolder *folder;
    GPtrArray *uids;
    ESource *source = NULL;
    ESource *default_source;
    GList *list, *iter;
    GtkWindow *parent;
    const gchar *extension_name;
    GError *error = NULL;

    folder = e_mail_reader_get_folder (reader);
    parent = e_mail_reader_get_window (reader);
    uids = e_mail_reader_get_selected_uids (reader);

    /* Ask before converting 10 or more mails to events. */
    if (uids->len > 10) {
        gchar *question;
        gint response;

        question = g_strdup_printf (
            get_question_add_all_mails (source_type, uids->len), uids->len);
        response = do_ask (question, FALSE);
        g_free (question);

        if (response == GTK_RESPONSE_NO) {
            em_utils_uids_free (uids);
            g_object_unref (folder);
            return;
        }
    }

    backend = e_mail_reader_get_backend (reader);
    shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
    registry = e_shell_get_registry (shell);

    switch (source_type) {
        case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
            extension_name = E_SOURCE_EXTENSION_CALENDAR;
            default_source = e_source_registry_ref_default_calendar (registry);
            break;
        case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
            extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
            default_source = e_source_registry_ref_default_memo_list (registry);
            break;
        case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
            extension_name = E_SOURCE_EXTENSION_TASK_LIST;
            default_source = e_source_registry_ref_default_task_list (registry);
            break;
        default:
            g_return_if_reached ();
    }

    list = e_source_registry_list_sources (registry, extension_name);

    /* If there is only one writable source, no need to prompt the user. */
    for (iter = list; iter != NULL; iter = g_list_next (iter)) {
        ESource *candidate = E_SOURCE (iter->data);

        if (e_source_get_writable (candidate)) {
            if (source == NULL)
                source = candidate;
            else {
                source = NULL;
                break;
            }
        }
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    if (source == NULL) {
        GtkWidget *dialog;
        ESourceSelector *selector;

        /* ask the user which tasks list to save to */
        dialog = e_source_selector_dialog_new (
            parent, registry, extension_name);

        selector = e_source_selector_dialog_get_selector (
            E_SOURCE_SELECTOR_DIALOG (dialog));

        e_source_selector_set_primary_selection (
            selector, default_source);

        if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
            source = e_source_selector_dialog_peek_primary_selection (
                E_SOURCE_SELECTOR_DIALOG (dialog));

        gtk_widget_destroy (dialog);
    } else if (!source && default_source) {
        source = default_source;
    } else if (!source) {
        e_notice (NULL, GTK_MESSAGE_ERROR, _("No writable calendar is available."));

        em_utils_uids_free (uids);
        g_object_unref (folder);
        if (error)
            g_error_free (error);
        goto exit;
    }

    if (source) {
        /* if a source has been selected, perform the mail2event operation */
        ECalClient *client = NULL;
        AsyncData *data = NULL;
        GThread *thread = NULL;
        GError *error = NULL;

        client = e_cal_client_new (source, source_type, &error);
        if (!client) {
            e_notice (
                parent, GTK_MESSAGE_ERROR,
                "Could not connect to '%s'",
                e_source_get_display_name (source));
            goto exit;
        }

        /* Fill the elements in AsynData */
        data = g_new0 (AsyncData, 1);
        data->client = client;
        data->folder = folder;
        data->uids = uids;
        data->with_attendees = with_attendees;

        if (uids->len == 1)
            data->selected_text = get_selected_text (reader);
        else
            data->selected_text = NULL;

        thread = g_thread_create ((GThreadFunc) do_mail_to_event, data, FALSE, &error);
        if (!thread) {
            g_warning (G_STRLOC ": %s", error->message);
            g_error_free (error);
        }
    }

exit:
    g_object_unref (default_source);
}

static void
action_mail_convert_to_event_cb (GtkAction *action,
                                 EMailReader *reader)
{
    mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE, reader);
}

static void
action_mail_convert_to_meeting_cb (GtkAction *action,
                                   EMailReader *reader)
{
    mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, TRUE, reader);
}

static void
action_mail_convert_to_memo_cb (GtkAction *action,
                                EMailReader *reader)
{
    mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_MEMOS, FALSE, reader);
}

static void
action_mail_convert_to_task_cb (GtkAction *action,
                                EMailReader *reader)
{
    mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_TASKS, FALSE, reader);
}

/* Note, we're not using EPopupActions here because we update the state
 * of entire actions groups instead of individual actions.  EPopupActions
 * just proxy the state of individual actions. */

static GtkActionEntry multi_selection_entries[] = {

    { "mail-convert-to-event",
      "appointment-new",
      N_("Create an _Event"),
      NULL,
      N_("Create a new event from the selected message"),
      G_CALLBACK (action_mail_convert_to_event_cb) },

    { "mail-convert-to-memo",
      "stock_insert-note",
      N_("Create a Mem_o"),
      NULL,
      N_("Create a new memo from the selected message"),
      G_CALLBACK (action_mail_convert_to_memo_cb) },

    { "mail-convert-to-task",
      "stock_todo",
      N_("Create a _Task"),
      NULL,
      N_("Create a new task from the selected message"),
      G_CALLBACK (action_mail_convert_to_task_cb) }
};

static GtkActionEntry single_selection_entries[] = {

    { "mail-convert-to-meeting",
      "stock_new-meeting",
      N_("Create a _Meeting"),
      NULL,
      N_("Create a new meeting from the selected message"),
      G_CALLBACK (action_mail_convert_to_meeting_cb) }
};

static void
update_actions_any_cb (EMailReader *reader,
                       guint32 state,
                       GtkActionGroup *action_group)
{
    gboolean sensitive;

    sensitive =
        (state & E_MAIL_READER_SELECTION_SINGLE) ||
        (state & E_MAIL_READER_SELECTION_MULTIPLE);

    gtk_action_group_set_sensitive (action_group, sensitive);
}

static void
update_actions_one_cb (EMailReader *reader,
                       guint32 state,
                       GtkActionGroup *action_group)
{
    gboolean sensitive;

    sensitive = (state & E_MAIL_READER_SELECTION_SINGLE);

    gtk_action_group_set_sensitive (action_group, sensitive);
}

static void
setup_actions (EMailReader *reader,
               GtkUIManager *ui_manager)
{
    GtkActionGroup *action_group;
    const gchar *domain = GETTEXT_PACKAGE;

    action_group = gtk_action_group_new ("mail-convert-any");
    gtk_action_group_set_translation_domain (action_group, domain);
    gtk_action_group_add_actions (
        action_group, multi_selection_entries,
        G_N_ELEMENTS (multi_selection_entries), reader);
    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    g_object_unref (action_group);

    /* GtkUIManager now owns the action group reference.
     * The signal we're connecting to will only be emitted
     * during the GtkUIManager's lifetime, so the action
     * group will not disappear on us. */

    g_signal_connect (
        reader, "update-actions",
        G_CALLBACK (update_actions_any_cb), action_group);

    action_group = gtk_action_group_new ("mail-convert-one");
    gtk_action_group_set_translation_domain (action_group, domain);
    gtk_action_group_add_actions (
        action_group, single_selection_entries,
        G_N_ELEMENTS (single_selection_entries), reader);
    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    g_object_unref (action_group);

    /* GtkUIManager now owns the action group reference.
     * The signal we're connecting to will only be emitted
     * during the GtkUIManager's lifetime, so the action
     * group will not disappear on us. */

    g_signal_connect (
        reader, "update-actions",
        G_CALLBACK (update_actions_one_cb), action_group);
}

gboolean
mail_browser_init (GtkUIManager *ui_manager,
                   EMailBrowser *browser)
{
    setup_actions (E_MAIL_READER (browser), ui_manager);

    return TRUE;
}

gboolean
mail_shell_view_init (GtkUIManager *ui_manager,
                      EShellView *shell_view)
{
    EShellContent *shell_content;

    shell_content = e_shell_view_get_shell_content (shell_view);

    setup_actions (E_MAIL_READER (shell_content), ui_manager);

    return TRUE;
}