aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/e-meeting-store.c
blob: ca4df0b854f9dbdbe445a8311c024b680cce0819 (plain) (tree)
1
2
3
4
5
6
7
8
9
  
                                                                


                                                               



                                                                    


                                                                   
                                                                             



                                             
                                               

                                                        
  





                    
                    
                       
                         


                                    
                                              

                                         
                                
                                   
                                    




                               


                                                       



                              
                           
                           
 




                                             
                      
 

                                 
                      
                              
 
                          
                          









                                                              
 


                           
                               
                        
 



                              


                    

                                       
                                

                           

  






                                                                         
 
                           
                                
 
                                                            
                                              
                                                            
                                         
                                                               
                                            
                                                           




                                        
              



















                                        
                                
 
                                                       
                                       
                                                                           
                                                
                                                                           
                                                
                                                                      




                                                
              
                                      












                                                 


               
                                   
 
                                                     




                             
              
                            







                                
                                        
 
                                                                  
                                                 
                                                                   
                                              
                                                                   
                                              
                                                                    
                                               
                                                                    
                                               
                                                                    
                                               
                                                                     




                                               
              
                                                  



















                                         









                                                                       
           







                                                             
                                               

























                                                                          
                 



                                                                         
                                                  












                                                           
                 












                                                                                         
 
           
                                                                           



                                   
                 




                                                                          
                                        




                                                                    
 


                                                    


                                                                    


                                                    

                                                                         


                                                    


                                                                   


                                                    


                                                                 


                                                    


                                                                 


                                                    


                                                                  


                                                    


                                                                    


                                                    


                                                                   


                                                    

                                                                     


                                                    

                                                                           






                                                          


                                                                            



                                                           


                                                                       





                                                  
                 
















                                                                                          
                                        





                                                       
 








                                                       
           


                                                              
 








                                                                                       
                                                                                    




                                                                 
                                        

































                                                                        
                                                                                      





                                                                                     
                                                    


                                                             








                                                                                         
                 
























                                                                                 


















                                                                   
           
                                                                       


                                      
 
                           
 
                                 


                                                            










                                                                                  
                    
                                           


                                                                    
                                             










                                                           











                                                            











                                                                       










                                                                





                                                            


















                                                                       













                                                                               












                                                                        






                                                                    






                                                                       
 
                                   
               
 
                                              


                                                                        
                                                 



                                              
                                            


                                                                    

                                                     
 

                                                        
 

                              
                                   
 
                                                     
                                                                         


           
                                                      


                                   











                                                                        
                                     
                             
                                          



                                            






















                                                    















                                             











                                            

 
           
                                           
 

                                                                   
 

                                                        

                                                           
 
                                            
 
                                     
 
                                                            







                                                         
            
                                                 
 

                                                                



                                   
                                                 
                                               
 


                                                      
                                                            


                                      

                                                     
 
                                     

                                                     

 





































                                                                                 

                                                             


                                                                
                                   


    

                                                                        


                                                      



                                                                 

 

                                                   


                                                                
                                 

 
    

                                                     


                                                      


                                                       

 


















                                                             






                                                               
 





                                                                                
 

































                                                                                        
                   
 









                                                                     
                     


                                                                      
 

























                                                                                  

         
                        



                                                                          
 
                                                                       
                                          





                                                           
                     
 










                                                                              
                                                     
                                                                          
                                          
 
                                                                     
                                          
         

 













































                                                                        
                  


                                                    

                                   
               
 

                            
 

                                                           
 
                                                                         
 
                                                                             


                                                             


















                                                                     




                                                                                     
 





                                                                                
 





                                               
                    

 
    



                                                             
                                           














                                                                
                          



                                 
 







                                                                                      
                                   
                                     


                                                                                       





                                                                   
 

                                    
 






                                          
                                                 

                             
               
 

                             
                                                      

                                                       
 

                                                                     
 
                                                           

         



                                            










                                                             
 



                                                                               
 












                                                                         
 



                                                                             
 












                                                                     
 





                                                                                
 



                                                                                     
 












                                                                      



                                                             


                              
 

















                                                                                  
 




                                                                                       
                                                              
 


                                                     





                                                    



                                          

                                             
                                               


                                                       
                                                           
 


                                                                                           
 

                                                                          
 












                                                                                              
 




                                       


                                                              

              
                                                                        










                                                       
                                                               
 
                
                           

                      

                        

                      

                                      
                             

                    


                       














                                                                                              




                                                   
                                     
                            
                                                        
                                                      
 
                          
                            


                                                                         
                                             
                                    


                                                                                                                     
                                    
                                               
 

                                                                   


                                                                 
                                        
 


                                                                        

                                    
                 
         





                                                                           
 

                                                            
                                                                   
 




                               

                                    
                                                     

                                                                                 

                                    
 





                                                                                         
                                    
                                                              






                                               
                    

 


                 

                                    
 



                                                      
               


                               
 




                                                                            
                                                               
 


                                                                    











                                                            
 




                                                                                 
 

                                            
                                 
                          
                            

                                   
                           

                                                            
 
                                                 
                           
                                        
 
                                            


                                                                  

                                                 
                                                                            

                                            


                                                                

                                               

                                                                          
 
                                                                                
 
         
 



                                            


                                                                                    

                                                                   


                                          



                                                    
                             
         
 

                    
 
           
                                                  


                                                          
                                 



                                      
               

                           
 
                                                            

                                                                
                       
 


                                                                           
                               
 


                                                                       
                               


                                   


                                                            
 







                                                                           

                                      





                                                               
                                     

                                                                            


                                                                              
                                                                          



                                                               

                                     







                                                                                 
                                                                     

                                             


                              
 

                                                             
 
                                                 
 
                                                                 
 



                                                                                               
 


                                                              


                       







                                                                               


                                                                     
         

 
           




                                        















































                                                                                    



                                                                         





                                                                            


                                                                     

                 





                                                                        



















































                                                                                       


                                                    

                                                              





                                                                      






































                                                                                         


                                                        




                                                                                  
                                                  

                                             





                                        
 
                                          





                                                                    






                                                                                          


                                                                                 
                                          
                                      


                       
                       
                                          

                                      


                                                                     

 






                                                                                 
               
 
                                                      
 



                                                                          
    
                                                           
                                               








                                                                             



                                                      
                                                             


                                        
/*
 * 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:
 *      JP Rosevear  <jpr@ximian.com>
 *      Mike Kestner  <mkestner@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

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

#include <gio/gio.h>
#include <glib/gi18n.h>
#include <libsoup/soup.h>
#include <libecal/e-cal-component.h>
#include <libecal/e-cal-util.h>
#include <libecal/e-cal-time-util.h>
#include <libedataserver/e-data-server-util.h>
#include <libedataserver/e-proxy.h>
#include <libedataserverui/e-passwords.h>
#include <e-util/e-extensible.h>
#include <e-util/e-account-utils.h>
#include <e-util/e-util-enumtypes.h>
#include "itip-utils.h"
#include "e-meeting-utils.h"
#include "e-meeting-attendee.h"
#include "e-meeting-store.h"

#define ROW_VALID(store, row) \
    (row >= 0 && row < store->priv->attendees->len)

struct _EMeetingStorePrivate {
    GPtrArray *attendees;
    gint stamp;

    ECalClient *client;
    icaltimezone *zone;

    gint default_reminder_interval;
    EDurationType default_reminder_units;

    gint week_start_day;

    gchar *fb_uri;

    GPtrArray *refresh_queue;
    GHashTable *refresh_data;
    GMutex *mutex;
    guint refresh_idle_id;

    guint num_threads;
    guint num_queries;
};

#define BUF_SIZE 1024

typedef struct _EMeetingStoreQueueData EMeetingStoreQueueData;
struct _EMeetingStoreQueueData {
    EMeetingStore *store;
    EMeetingAttendee *attendee;

    gboolean refreshing;

    EMeetingTime start;
    EMeetingTime end;

    gchar buffer[BUF_SIZE];
    GString *string;

    GPtrArray *call_backs;
    GPtrArray *data;
};

enum {
    PROP_0,
    PROP_CLIENT,
    PROP_DEFAULT_REMINDER_INTERVAL,
    PROP_DEFAULT_REMINDER_UNITS,
    PROP_FREE_BUSY_TEMPLATE,
    PROP_TIMEZONE,
    PROP_WEEK_START_DAY
};

/* Forward Declarations */
static void ems_tree_model_init (GtkTreeModelIface *iface);

G_DEFINE_TYPE_WITH_CODE (
    EMeetingStore, e_meeting_store, GTK_TYPE_LIST_STORE,
    G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
    G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, ems_tree_model_init))

static icalparameter_cutype
text_to_type (const gchar *type)
{
    if (!e_util_utf8_strcasecmp (type, _("Individual")))
        return ICAL_CUTYPE_INDIVIDUAL;
    else if (!e_util_utf8_strcasecmp (type, _("Group")))
        return ICAL_CUTYPE_GROUP;
    else if (!e_util_utf8_strcasecmp (type, _("Resource")))
        return ICAL_CUTYPE_RESOURCE;
    else if (!e_util_utf8_strcasecmp (type, _("Room")))
        return ICAL_CUTYPE_ROOM;
    else
        return ICAL_CUTYPE_NONE;
}

static gchar *
type_to_text (icalparameter_cutype type)
{
    switch (type) {
    case ICAL_CUTYPE_INDIVIDUAL:
        return _("Individual");
    case ICAL_CUTYPE_GROUP:
        return _("Group");
    case ICAL_CUTYPE_RESOURCE:
        return _("Resource");
    case ICAL_CUTYPE_ROOM:
        return _("Room");
    default:
        return _("Unknown");
    }

    return NULL;

}

static icalparameter_role
text_to_role (const gchar *role)
{
    if (!e_util_utf8_strcasecmp (role, _("Chair")))
        return ICAL_ROLE_CHAIR;
    else if (!e_util_utf8_strcasecmp (role, _("Required Participant")))
        return ICAL_ROLE_REQPARTICIPANT;
    else if (!e_util_utf8_strcasecmp (role, _("Optional Participant")))
        return ICAL_ROLE_OPTPARTICIPANT;
    else if (!e_util_utf8_strcasecmp (role, _("Non-Participant")))
        return ICAL_ROLE_NONPARTICIPANT;
    else
        return ICAL_ROLE_NONE;
}

static gchar *
role_to_text (icalparameter_role role)
{
    switch (role) {
    case ICAL_ROLE_CHAIR:
        return _("Chair");
    case ICAL_ROLE_REQPARTICIPANT:
        return _("Required Participant");
    case ICAL_ROLE_OPTPARTICIPANT:
        return _("Optional Participant");
    case ICAL_ROLE_NONPARTICIPANT:
        return _("Non-Participant");
    default:
        return _("Unknown");
    }
}

static gboolean
text_to_boolean (const gchar *role)
{
    if (!e_util_utf8_strcasecmp (role, _("Yes")))
        return TRUE;
    else
        return FALSE;
}

static gchar *
boolean_to_text (gboolean b)
{
    if (b)
        return _("Yes");
    else
        return _("No");
}

static icalparameter_partstat
text_to_partstat (const gchar *partstat)
{
    if (!e_util_utf8_strcasecmp (partstat, _("Needs Action")))
        return ICAL_PARTSTAT_NEEDSACTION;
    else if (!e_util_utf8_strcasecmp (partstat, _("Accepted")))
        return ICAL_PARTSTAT_ACCEPTED;
    else if (!e_util_utf8_strcasecmp (partstat, _("Declined")))
        return ICAL_PARTSTAT_DECLINED;
    else if (!e_util_utf8_strcasecmp (partstat, _("Tentative")))
        return ICAL_PARTSTAT_TENTATIVE;
    else if (!e_util_utf8_strcasecmp (partstat, _("Delegated")))
        return ICAL_PARTSTAT_DELEGATED;
    else if (!e_util_utf8_strcasecmp (partstat, _("Completed")))
        return ICAL_PARTSTAT_COMPLETED;
    else if (!e_util_utf8_strcasecmp (partstat, _("In Process")))
        return ICAL_PARTSTAT_INPROCESS;
    else
        return ICAL_PARTSTAT_NONE;
}

static gchar *
partstat_to_text (icalparameter_partstat partstat)
{
    switch (partstat) {
    case ICAL_PARTSTAT_NEEDSACTION:
        return _("Needs Action");
    case ICAL_PARTSTAT_ACCEPTED:
        return _("Accepted");
    case ICAL_PARTSTAT_DECLINED:
        return _("Declined");
    case ICAL_PARTSTAT_TENTATIVE:
        return _("Tentative");
    case ICAL_PARTSTAT_DELEGATED:
        return _("Delegated");
    case ICAL_PARTSTAT_COMPLETED:
        return _("Completed");
    case ICAL_PARTSTAT_INPROCESS:
        return _("In Process");
    case ICAL_PARTSTAT_NONE:
    default:
        return _("Unknown");
    }
}

static GtkTreeModelFlags
get_flags (GtkTreeModel *model)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (model), 0);

    return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
}

static gint
get_n_columns (GtkTreeModel *model)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (model), 0);

    return E_MEETING_STORE_COLUMN_COUNT;
}

static GType
get_column_type (GtkTreeModel *model, gint col)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (model), G_TYPE_INVALID);

    switch (col) {
    case E_MEETING_STORE_ADDRESS_COL:
    case E_MEETING_STORE_MEMBER_COL:
    case E_MEETING_STORE_TYPE_COL:
    case E_MEETING_STORE_ROLE_COL:
    case E_MEETING_STORE_RSVP_COL:
    case E_MEETING_STORE_DELTO_COL:
    case E_MEETING_STORE_DELFROM_COL:
    case E_MEETING_STORE_STATUS_COL:
    case E_MEETING_STORE_CN_COL:
    case E_MEETING_STORE_LANGUAGE_COL:
    case E_MEETING_STORE_ATTENDEE_COL:
        return G_TYPE_STRING;
    case E_MEETING_STORE_ATTENDEE_UNDERLINE_COL:
        return PANGO_TYPE_UNDERLINE;
    default:
        return G_TYPE_INVALID;
    }
}

static gboolean
get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
{
    gint row;

    g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);
    g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

    row = gtk_tree_path_get_indices (path)[0];

    if (!ROW_VALID (E_MEETING_STORE (model), row))
           return FALSE;

    iter->stamp = E_MEETING_STORE (model)->priv->stamp;
    iter->user_data = GINT_TO_POINTER (row);

    return TRUE;
}

static GtkTreePath *
get_path (GtkTreeModel *model, GtkTreeIter *iter)
{
    gint row;
    GtkTreePath *result;

    g_return_val_if_fail (E_IS_MEETING_STORE (model), NULL);
    g_return_val_if_fail (iter->stamp == E_MEETING_STORE (model)->priv->stamp, NULL);

    row = GPOINTER_TO_INT (iter->user_data);

    g_return_val_if_fail (ROW_VALID (E_MEETING_STORE (model), row), NULL);

    result = gtk_tree_path_new ();
    gtk_tree_path_append_index (result, row);
    return result;
}

static void
get_value (GtkTreeModel *model, GtkTreeIter *iter, gint col, GValue *value)
{
    EMeetingStore *store;
    EMeetingAttendee *attendee;
    const gchar *cn;
    gint row;

    g_return_if_fail (E_IS_MEETING_STORE (model));
    g_return_if_fail (col >= 0 && col < E_MEETING_STORE_COLUMN_COUNT);

    row = GPOINTER_TO_INT (iter->user_data);
    store = E_MEETING_STORE (model);

    g_return_if_fail (iter->stamp == store->priv->stamp);
    g_return_if_fail (ROW_VALID (E_MEETING_STORE (model), row));

    attendee = g_ptr_array_index (store->priv->attendees, row);

    switch (col) {
    case E_MEETING_STORE_ADDRESS_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, itip_strip_mailto (
            e_meeting_attendee_get_address (attendee)));
        break;
    case E_MEETING_STORE_MEMBER_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, e_meeting_attendee_get_member (attendee));
        break;
    case E_MEETING_STORE_TYPE_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, type_to_text (
            e_meeting_attendee_get_cutype (attendee)));
        break;
    case E_MEETING_STORE_ROLE_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, role_to_text (
            e_meeting_attendee_get_role (attendee)));
        break;
    case E_MEETING_STORE_RSVP_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, boolean_to_text (
            e_meeting_attendee_get_rsvp (attendee)));
        break;
    case E_MEETING_STORE_DELTO_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, itip_strip_mailto (
            e_meeting_attendee_get_delto (attendee)));
        break;
    case E_MEETING_STORE_DELFROM_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, itip_strip_mailto (
            e_meeting_attendee_get_delfrom (attendee)));
        break;
    case E_MEETING_STORE_STATUS_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, partstat_to_text (
            e_meeting_attendee_get_status (attendee)));
        break;
    case E_MEETING_STORE_CN_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, e_meeting_attendee_get_cn (attendee));
        break;
    case E_MEETING_STORE_LANGUAGE_COL:
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (
            value, e_meeting_attendee_get_language (attendee));
        break;
    case E_MEETING_STORE_ATTENDEE_COL:
        g_value_init (value, G_TYPE_STRING);
        cn = e_meeting_attendee_get_cn (attendee);
        if (strcmp (cn, ""))
            g_value_set_string (value, cn);
        else
            g_value_set_string (
                value, itip_strip_mailto (
                e_meeting_attendee_get_address (attendee)));
        break;
    case E_MEETING_STORE_ATTENDEE_UNDERLINE_COL:
        cn = e_meeting_attendee_get_cn (attendee);
        g_value_init (value, PANGO_TYPE_UNDERLINE);
        g_value_set_enum (
            value, strcmp ("", cn) == 0 ?
            PANGO_UNDERLINE_NONE : PANGO_UNDERLINE_SINGLE);
    }
}

static gboolean
iter_next (GtkTreeModel *model, GtkTreeIter *iter)
{
    gint row;

    g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);
    g_return_val_if_fail (iter->stamp == E_MEETING_STORE (model)->priv->stamp, FALSE);

    row = GPOINTER_TO_INT (iter->user_data) + 1;
    iter->user_data = GINT_TO_POINTER (row);

    return ROW_VALID (E_MEETING_STORE (model), row);
}

static gboolean
iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
{
    EMeetingStore *store;

    g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);

    store = E_MEETING_STORE (model);

    if (parent || store->priv->attendees->len <= 0)
        return FALSE;

    iter->stamp = store->priv->stamp;
    iter->user_data = GINT_TO_POINTER (0);

    return TRUE;
}

static gboolean
iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
{
    return FALSE;
}

static gint
iter_n_children (GtkTreeModel *model, GtkTreeIter *iter)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (model), -1);

    if (!iter)
        return E_MEETING_STORE (model)->priv->attendees->len;

    g_return_val_if_fail (iter->stamp == E_MEETING_STORE (model)->priv->stamp, -1);

    return 0;
}

static gboolean
iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
{
    EMeetingStore *store;

    g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);

    store = E_MEETING_STORE (model);

    if (parent || !ROW_VALID (store, n))
        return FALSE;

    iter->stamp = store->priv->stamp;
    iter->user_data = GINT_TO_POINTER (n);

    return TRUE;
}

static gboolean
iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child)
{
    return FALSE;
}

static void
ems_tree_model_init (GtkTreeModelIface *iface)
{
    iface->get_flags = get_flags;
    iface->get_n_columns = get_n_columns;
    iface->get_column_type = get_column_type;
    iface->get_iter = get_iter;
    iface->get_path = get_path;
    iface->get_value = get_value;
    iface->iter_next = iter_next;
    iface->iter_children = iter_children;
    iface->iter_has_child = iter_has_child;
    iface->iter_n_children = iter_n_children;
    iface->iter_nth_child = iter_nth_child;
    iface->iter_parent = iter_parent;
}

void
e_meeting_store_set_value (EMeetingStore *store, gint row, gint col, const gchar *val)
{
    icalparameter_cutype type;
    EMeetingAttendee *attendee = g_ptr_array_index (store->priv->attendees, row);

    switch (col) {
    case E_MEETING_STORE_ADDRESS_COL:
        if (val != NULL && *((gchar *) val))
            e_meeting_attendee_set_address (
                attendee, g_strdup_printf (
                "MAILTO:%s", (gchar *) val));
        break;
    case E_MEETING_STORE_MEMBER_COL:
        e_meeting_attendee_set_member (attendee, g_strdup (val));
        break;
    case E_MEETING_STORE_TYPE_COL:
        type = text_to_type (val);
        e_meeting_attendee_set_cutype (attendee, text_to_type (val));
        if (type == ICAL_CUTYPE_RESOURCE) {
            e_meeting_attendee_set_role (attendee, ICAL_ROLE_NONPARTICIPANT);
        }
        break;
    case E_MEETING_STORE_ROLE_COL:
        e_meeting_attendee_set_role (attendee, text_to_role (val));
        break;
    case E_MEETING_STORE_RSVP_COL:
        e_meeting_attendee_set_rsvp (attendee, text_to_boolean (val));
        break;
    case E_MEETING_STORE_DELTO_COL:
        e_meeting_attendee_set_delto (attendee, g_strdup (val));
        break;
    case E_MEETING_STORE_DELFROM_COL:
        e_meeting_attendee_set_delfrom (attendee, g_strdup (val));
        break;
    case E_MEETING_STORE_STATUS_COL:
        e_meeting_attendee_set_status (attendee, text_to_partstat (val));
        break;
    case E_MEETING_STORE_CN_COL:
        e_meeting_attendee_set_cn (attendee, g_strdup (val));
        break;
    case E_MEETING_STORE_LANGUAGE_COL:
        e_meeting_attendee_set_language (attendee, g_strdup (val));
        break;
    }
}

struct FindAttendeeData
{
    EMeetingAttendee *find;
    EMeetingStoreQueueData *qdata;
};

static void
find_attendee_cb (gpointer key, gpointer value, gpointer user_data)
{
    EMeetingStoreQueueData *qdata = value;
    struct FindAttendeeData *fad = user_data;

    g_return_if_fail (qdata != NULL);
    g_return_if_fail (fad != NULL);

    if (qdata->attendee == fad->find)
        fad->qdata = qdata;
}

static void
refresh_queue_remove (EMeetingStore *store, EMeetingAttendee *attendee)
{
    EMeetingStorePrivate *priv;
    EMeetingStoreQueueData *qdata;

    priv = store->priv;

    /* Free the queue data */
    qdata = g_hash_table_lookup (
        priv->refresh_data, itip_strip_mailto (
        e_meeting_attendee_get_address (attendee)));
    if (!qdata) {
        struct FindAttendeeData fad = { 0 };

        fad.find = attendee;
        fad.qdata = NULL;

        g_hash_table_foreach (priv->refresh_data, find_attendee_cb, &fad);

        qdata = fad.qdata;
    }

    if (qdata) {
        g_mutex_lock (priv->mutex);
        g_hash_table_remove (
            priv->refresh_data, itip_strip_mailto (
            e_meeting_attendee_get_address (attendee)));
        g_mutex_unlock (priv->mutex);
        g_ptr_array_free (qdata->call_backs, TRUE);
        g_ptr_array_free (qdata->data, TRUE);
        g_free (qdata);
    }

    /* Unref the attendee */
    g_ptr_array_remove (priv->refresh_queue, attendee);
    g_object_unref (attendee);
}

static void
meeting_store_set_property (GObject *object,
                            guint property_id,
                            const GValue *value,
                            GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_CLIENT:
            e_meeting_store_set_client (
                E_MEETING_STORE (object),
                g_value_get_object (value));
            return;

        case PROP_DEFAULT_REMINDER_INTERVAL:
            e_meeting_store_set_default_reminder_interval (
                E_MEETING_STORE (object),
                g_value_get_int (value));
            return;

        case PROP_DEFAULT_REMINDER_UNITS:
            e_meeting_store_set_default_reminder_units (
                E_MEETING_STORE (object),
                g_value_get_enum (value));
            return;

        case PROP_FREE_BUSY_TEMPLATE:
            e_meeting_store_set_free_busy_template (
                E_MEETING_STORE (object),
                g_value_get_string (value));
            return;

        case PROP_TIMEZONE:
            e_meeting_store_set_timezone (
                E_MEETING_STORE (object),
                g_value_get_pointer (value));
            return;

        case PROP_WEEK_START_DAY:
            e_meeting_store_set_week_start_day (
                E_MEETING_STORE (object),
                g_value_get_int (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
meeting_store_get_property (GObject *object,
                            guint property_id,
                            GValue *value,
                            GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_CLIENT:
            g_value_set_object (
                value,
                e_meeting_store_get_client (
                E_MEETING_STORE (object)));
            return;

        case PROP_DEFAULT_REMINDER_INTERVAL:
            g_value_set_int (
                value,
                e_meeting_store_get_default_reminder_interval (
                E_MEETING_STORE (object)));
            return;

        case PROP_DEFAULT_REMINDER_UNITS:
            g_value_set_enum (
                value,
                e_meeting_store_get_default_reminder_units (
                E_MEETING_STORE (object)));
            return;

        case PROP_FREE_BUSY_TEMPLATE:
            g_value_set_string (
                value,
                e_meeting_store_get_free_busy_template (
                E_MEETING_STORE (object)));
            return;

        case PROP_TIMEZONE:
            g_value_set_pointer (
                value,
                e_meeting_store_get_timezone (
                E_MEETING_STORE (object)));
            return;

        case PROP_WEEK_START_DAY:
            g_value_set_int (
                value,
                e_meeting_store_get_week_start_day (
                E_MEETING_STORE (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
meeting_store_finalize (GObject *object)
{
    EMeetingStorePrivate *priv;
    gint i;

    priv = E_MEETING_STORE (object)->priv;

    for (i = 0; i < priv->attendees->len; i++)
        g_object_unref (g_ptr_array_index (priv->attendees, i));
    g_ptr_array_free (priv->attendees, TRUE);

    if (priv->client != NULL)
        g_object_unref (priv->client);

    while (priv->refresh_queue->len > 0)
        refresh_queue_remove (
            E_MEETING_STORE (object),
            g_ptr_array_index (priv->refresh_queue, 0));
    g_ptr_array_free (priv->refresh_queue, TRUE);
    g_hash_table_destroy (priv->refresh_data);

    if (priv->refresh_idle_id)
        g_source_remove (priv->refresh_idle_id);

    g_free (priv->fb_uri);

    g_mutex_free (priv->mutex);

    /* Chain up to parent's finalize() method. */
    G_OBJECT_CLASS (e_meeting_store_parent_class)->finalize (object);
}

static void
e_meeting_store_class_init (EMeetingStoreClass *class)
{
    GObjectClass *object_class;

    g_type_class_add_private (class, sizeof (EMeetingStorePrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = meeting_store_set_property;
    object_class->get_property = meeting_store_get_property;
    object_class->finalize = meeting_store_finalize;

    g_object_class_install_property (
        object_class,
        PROP_CLIENT,
        g_param_spec_object (
            "client",
            "ECalClient",
            NULL,
            E_TYPE_CAL_CLIENT,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_DEFAULT_REMINDER_INTERVAL,
        g_param_spec_int (
            "default-reminder-interval",
            "Default Reminder Interval",
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_DEFAULT_REMINDER_UNITS,
        g_param_spec_enum (
            "default-reminder-units",
            "Default Reminder Units",
            NULL,
            E_TYPE_DURATION_TYPE,
            E_DURATION_MINUTES,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_FREE_BUSY_TEMPLATE,
        g_param_spec_string (
            "free-busy-template",
            "Free/Busy Template",
            NULL,
            NULL,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_TIMEZONE,
        g_param_spec_pointer (
            "timezone",
            "Timezone",
            NULL,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_WEEK_START_DAY,
        g_param_spec_int (
            "week-start-day",
            "Week Start Day",
            NULL,
            0,  /* Monday */
            6,  /* Sunday */
            0,
            G_PARAM_READWRITE));
}

static void
e_meeting_store_init (EMeetingStore *store)
{
    store->priv = G_TYPE_INSTANCE_GET_PRIVATE (
        store, E_TYPE_MEETING_STORE, EMeetingStorePrivate);

    store->priv->attendees = g_ptr_array_new ();
    store->priv->refresh_queue = g_ptr_array_new ();
    store->priv->refresh_data = g_hash_table_new_full (
        g_str_hash, g_str_equal, g_free, NULL);

    store->priv->mutex = g_mutex_new ();

    store->priv->num_queries = 0;

    e_extensible_load_extensions (E_EXTENSIBLE (store));
}

GObject *
e_meeting_store_new (void)
{
    return g_object_new (E_TYPE_MEETING_STORE, NULL);
}

ECalClient *
e_meeting_store_get_client (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);

    return store->priv->client;
}

void
e_meeting_store_set_client (EMeetingStore *store,
                            ECalClient *client)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    if (client != NULL) {
        g_return_if_fail (E_IS_CAL_CLIENT (client));
        g_object_ref (client);
    }

    if (store->priv->client != NULL)
        g_object_unref (store->priv->client);

    store->priv->client = client;

    g_object_notify (G_OBJECT (store), "client");
}

gint
e_meeting_store_get_default_reminder_interval (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);

    return store->priv->default_reminder_interval;
}

void
e_meeting_store_set_default_reminder_interval (EMeetingStore *store,
                                               gint default_reminder_interval)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    store->priv->default_reminder_interval = default_reminder_interval;

    g_object_notify (G_OBJECT (store), "default-reminder-interval");
}

EDurationType
e_meeting_store_get_default_reminder_units (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);

    return store->priv->default_reminder_units;
}

void
e_meeting_store_set_default_reminder_units (EMeetingStore *store,
                                            EDurationType default_reminder_units)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    store->priv->default_reminder_units = default_reminder_units;

    g_object_notify (G_OBJECT (store), "default-reminder-units");
}

const gchar *
e_meeting_store_get_free_busy_template (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);

    return store->priv->fb_uri;
}

void
e_meeting_store_set_free_busy_template (EMeetingStore *store,
                                        const gchar *free_busy_template)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    g_free (store->priv->fb_uri);
    store->priv->fb_uri = g_strdup (free_busy_template);

    g_object_notify (G_OBJECT (store), "free-busy-template");
}

icaltimezone *
e_meeting_store_get_timezone (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);

    return store->priv->zone;
}

void
e_meeting_store_set_timezone (EMeetingStore *store,
                              icaltimezone *timezone)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    store->priv->zone = timezone;

    g_object_notify (G_OBJECT (store), "timezone");
}

gint
e_meeting_store_get_week_start_day (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);

    return store->priv->week_start_day;
}

void
e_meeting_store_set_week_start_day (EMeetingStore *store,
                                    gint week_start_day)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    store->priv->week_start_day = week_start_day;

    g_object_notify (G_OBJECT (store), "week-start-day");
}

static void
attendee_changed_cb (EMeetingAttendee *attendee, gpointer data)
{
    EMeetingStore *store = E_MEETING_STORE (data);
    GtkTreePath *path;
    GtkTreeIter iter;
    gint row = -1, i;

    for (i = 0; i < store->priv->attendees->len; i++) {
        if (attendee == g_ptr_array_index (store->priv->attendees, i)) {
            row = i;
            break;
        }
    }

    if (row == -1)
        return;

    path = gtk_tree_path_new ();
    gtk_tree_path_append_index (path, row);
    get_iter (GTK_TREE_MODEL (store), &iter, path);
    gtk_tree_model_row_changed (GTK_TREE_MODEL (store), path, &iter);
    gtk_tree_path_free (path);
}

void
e_meeting_store_add_attendee (EMeetingStore *store, EMeetingAttendee *attendee)
{
    GtkTreePath *path;
    GtkTreeIter iter;

    g_return_if_fail (E_IS_MEETING_STORE (store));

    g_object_ref (attendee);
    g_ptr_array_add (store->priv->attendees, attendee);

    g_signal_connect (attendee, "changed", G_CALLBACK (attendee_changed_cb), store);

    path = gtk_tree_path_new ();
    gtk_tree_path_append_index (path, store->priv->attendees->len - 1);
    get_iter (GTK_TREE_MODEL (store), &iter, path);
    gtk_tree_model_row_inserted (GTK_TREE_MODEL (store), path, &iter);
    gtk_tree_path_free (path);
}

EMeetingAttendee *
e_meeting_store_add_attendee_with_defaults (EMeetingStore *store)
{
    EMeetingAttendee *attendee;
    gchar *str;

    attendee = E_MEETING_ATTENDEE (e_meeting_attendee_new ());

    e_meeting_attendee_set_address (attendee, g_strdup (""));
    e_meeting_attendee_set_member (attendee, g_strdup (""));

    str = g_strdup (_("Individual"));
    e_meeting_attendee_set_cutype (attendee, text_to_type (str));
    g_free (str);
    str = g_strdup (_("Required Participant"));
    e_meeting_attendee_set_role (attendee, text_to_role (str));
    g_free (str);
    str = g_strdup (_("Yes"));
    e_meeting_attendee_set_rsvp (attendee, text_to_boolean (str));
    g_free (str);

    e_meeting_attendee_set_delto (attendee, g_strdup (""));
    e_meeting_attendee_set_delfrom (attendee, g_strdup (""));

    str = g_strdup (_("Needs Action"));
    e_meeting_attendee_set_status (attendee, text_to_partstat (str));
    g_free (str);

    e_meeting_attendee_set_cn (attendee, g_strdup (""));
    e_meeting_attendee_set_language (attendee, g_strdup ("en"));

    e_meeting_store_add_attendee (store, attendee);

    return attendee;
}

void
e_meeting_store_remove_attendee (EMeetingStore *store, EMeetingAttendee *attendee)
{
    gint i, row = -1;
    GtkTreePath *path;

    for (i = 0; i < store->priv->attendees->len; i++) {
        if (attendee == g_ptr_array_index (store->priv->attendees, i)) {
            row = i;
            break;
        }
    }

    if (row != -1) {
        path = gtk_tree_path_new ();
        gtk_tree_path_append_index (path, row);
        gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), path);
        gtk_tree_path_free (path);

        g_ptr_array_remove_index (store->priv->attendees, row);
        g_object_unref (attendee);
    }
}

void
e_meeting_store_remove_all_attendees (EMeetingStore *store)
{
    gint i, j, k;

    for (i = 0, j = e_meeting_store_count_actual_attendees (store), k = 0;
         i < j; i++) {
        /* Always try to remove the attendee at index 0 since
         * it is the only one that will continue to exist until
         * all attendees are removed. */
        EMeetingAttendee *attendee;
        GtkTreePath *path;

        attendee = g_ptr_array_index (store->priv->attendees, k);

        path = gtk_tree_path_new ();
        gtk_tree_path_append_index (path, k);
        gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), path);
        gtk_tree_path_free (path);

        g_ptr_array_remove_index (store->priv->attendees, k);
        g_object_unref (attendee);
    }
}

/**
 * e_meeting_store_find_self:
 * @store: an #EMeetingStore
 * @row: return location for the matching row number, or %NULL
 *
 * Looks for the user in @store by comparing attendee email addresses to
 * registered mail identities.  If a matching email address is found and
 * @row is not %NULL, @row will be set to the #EMeetingStore row number
 * with the matching email address.
 *
 * Returns: an #EMeetingAttendee, or %NULL
 **/
EMeetingAttendee *
e_meeting_store_find_self (EMeetingStore *store,
                           gint *row)
{
    EMeetingAttendee *attendee = NULL;
    EAccountList *account_list;
    EIterator *iterator;

    g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);

    account_list = e_get_account_list ();

    iterator = e_list_get_iterator (E_LIST (account_list));

    while (e_iterator_is_valid (iterator)) {
        EAccount *account;

        /* XXX EIterator misuses const. */
        account = (EAccount *) e_iterator_get (iterator);

        attendee = e_meeting_store_find_attendee (
            store, account->id->address, row);

        if (attendee != NULL)
            break;

        e_iterator_next (iterator);
    }

    g_object_unref (iterator);

    return attendee;
}

EMeetingAttendee *
e_meeting_store_find_attendee (EMeetingStore *store,
                               const gchar *address,
                               gint *row)
{
    EMeetingAttendee *attendee;
    gint i;

    if (address == NULL)
        return NULL;

    for (i = 0; i < store->priv->attendees->len; i++) {
        const gchar *attendee_address;

        attendee = g_ptr_array_index (store->priv->attendees, i);

        attendee_address = e_meeting_attendee_get_address (attendee);
        if (attendee_address && !g_ascii_strcasecmp (
            itip_strip_mailto (attendee_address),
            itip_strip_mailto (address))) {
            if (row != NULL)
                *row = i;

            return attendee;
        }
    }

    return NULL;
}

EMeetingAttendee *
e_meeting_store_find_attendee_at_row (EMeetingStore *store, gint row)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
    g_return_val_if_fail (ROW_VALID (store, row), NULL);

    return g_ptr_array_index (store->priv->attendees, row);
}

GtkTreePath *
e_meeting_store_find_attendee_path (EMeetingStore *store, EMeetingAttendee *attendee)
{
    GtkTreePath *path;
    gint row = -1, i;

    for (i = 0; i < store->priv->attendees->len; i++) {
        if (attendee == g_ptr_array_index (store->priv->attendees, i)) {
            row = i;
            break;
        }
    }

    if (row == -1)
        return NULL;

    path = gtk_tree_path_new ();
    gtk_tree_path_append_index (path, row);

    return path;
}

gint
e_meeting_store_count_actual_attendees (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);

    return store->priv->attendees->len;
}

const GPtrArray *
e_meeting_store_get_attendees (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);

    return store->priv->attendees;
}

static icaltimezone *
find_zone (icalproperty *ip, icalcomponent *tz_top_level)
{
    icalparameter *param;
    icalcomponent *sub_comp;
    const gchar *tzid;
    icalcompiter iter;

    if (tz_top_level == NULL)
        return NULL;

    param = icalproperty_get_first_parameter (ip, ICAL_TZID_PARAMETER);
    if (param == NULL)
        return NULL;
    tzid = icalparameter_get_tzid (param);

    iter = icalcomponent_begin_component (tz_top_level, ICAL_VTIMEZONE_COMPONENT);
    while ((sub_comp = icalcompiter_deref (&iter)) != NULL) {
        icalcomponent *clone;
        icalproperty *prop;
        const gchar *tz_tzid;

        prop = icalcomponent_get_first_property (sub_comp, ICAL_TZID_PROPERTY);
        tz_tzid = icalproperty_get_tzid (prop);
        if (!strcmp (tzid, tz_tzid)) {
            icaltimezone *zone;

            zone = icaltimezone_new ();
            clone = icalcomponent_new_clone (sub_comp);
            icaltimezone_set_component (zone, clone);

            return zone;
        }

        icalcompiter_next (&iter);
    }

    return NULL;
}

static void
process_callbacks (EMeetingStoreQueueData *qdata)
{
    EMeetingStore *store;
    gint i;

    store = qdata->store;

    for (i = 0; i < qdata->call_backs->len; i++) {
        EMeetingStoreRefreshCallback call_back;
        gpointer *data = NULL;

        call_back = g_ptr_array_index (qdata->call_backs, i);
        data = g_ptr_array_index (qdata->data, i);

        g_idle_add ((GSourceFunc) call_back, data);
    }

    g_mutex_lock (store->priv->mutex);
    store->priv->num_threads--;
    g_mutex_unlock (store->priv->mutex);

    refresh_queue_remove (qdata->store, qdata->attendee);
    g_object_unref (store);
}

static void
process_free_busy_comp (EMeetingAttendee *attendee,
            icalcomponent *fb_comp,
            icaltimezone *zone,
            icalcomponent *tz_top_level)
{
    icalproperty *ip;

    ip = icalcomponent_get_first_property (fb_comp, ICAL_DTSTART_PROPERTY);
    if (ip != NULL) {
        struct icaltimetype dtstart;
        icaltimezone *ds_zone;

        dtstart = icalproperty_get_dtstart (ip);
        if (!dtstart.is_utc)
            ds_zone = find_zone (ip, tz_top_level);
        else
            ds_zone = icaltimezone_get_utc_timezone ();
        icaltimezone_convert_time (&dtstart, ds_zone, zone);
        e_meeting_attendee_set_start_busy_range (attendee,
                             dtstart.year,
                             dtstart.month,
                             dtstart.day,
                             dtstart.hour,
                             dtstart.minute);
    }

    ip = icalcomponent_get_first_property (fb_comp, ICAL_DTEND_PROPERTY);
    if (ip != NULL) {
        struct icaltimetype dtend;
        icaltimezone *de_zone;

        dtend = icalproperty_get_dtend (ip);
        if (!dtend.is_utc)
            de_zone = find_zone (ip, tz_top_level);
        else
            de_zone = icaltimezone_get_utc_timezone ();
        icaltimezone_convert_time (&dtend, de_zone, zone);
        e_meeting_attendee_set_end_busy_range (attendee,
                               dtend.year,
                               dtend.month,
                               dtend.day,
                               dtend.hour,
                               dtend.minute);
    }

    ip = icalcomponent_get_first_property (fb_comp, ICAL_FREEBUSY_PROPERTY);
    while (ip != NULL) {
        icalparameter *param;
        struct icalperiodtype fb;
        EMeetingFreeBusyType busy_type = E_MEETING_FREE_BUSY_LAST;
        icalparameter_fbtype fbtype = ICAL_FBTYPE_BUSY;

        fb = icalproperty_get_freebusy (ip);
        param = icalproperty_get_first_parameter (ip, ICAL_FBTYPE_PARAMETER);
        if (param != NULL)
            fbtype =  icalparameter_get_fbtype (param);

        switch (fbtype) {
        case ICAL_FBTYPE_BUSY:
            busy_type = E_MEETING_FREE_BUSY_BUSY;
            break;

        case ICAL_FBTYPE_BUSYUNAVAILABLE:
            busy_type = E_MEETING_FREE_BUSY_OUT_OF_OFFICE;
            break;

        case ICAL_FBTYPE_BUSYTENTATIVE:
            busy_type = E_MEETING_FREE_BUSY_TENTATIVE;
            break;

        case ICAL_FBTYPE_FREE:
            busy_type = E_MEETING_FREE_BUSY_FREE;
            break;

        default:
            break;
        }

        if (busy_type != E_MEETING_FREE_BUSY_LAST) {
            icaltimezone *utc_zone = icaltimezone_get_utc_timezone ();

            icaltimezone_convert_time (&fb.start, utc_zone, zone);
            icaltimezone_convert_time (&fb.end, utc_zone, zone);
            e_meeting_attendee_add_busy_period (attendee,
                                fb.start.year,
                                fb.start.month,
                                fb.start.day,
                                fb.start.hour,
                                fb.start.minute,
                                fb.end.year,
                                fb.end.month,
                                fb.end.day,
                                fb.end.hour,
                                fb.end.minute,
                                busy_type);
        }

        ip = icalcomponent_get_next_property (fb_comp, ICAL_FREEBUSY_PROPERTY);
    }
}

static void
process_free_busy (EMeetingStoreQueueData *qdata, gchar *text)
{
    EMeetingStore *store = qdata->store;
    EMeetingStorePrivate *priv;
    EMeetingAttendee *attendee = qdata->attendee;
    icalcomponent *main_comp;
    icalcomponent_kind kind = ICAL_NO_COMPONENT;

    priv = store->priv;

    main_comp = icalparser_parse_string (text);
    if (main_comp == NULL) {
        process_callbacks (qdata);
        return;
    }

    kind = icalcomponent_isa (main_comp);
    if (kind == ICAL_VCALENDAR_COMPONENT) {
        icalcompiter iter;
        icalcomponent *tz_top_level, *sub_comp;

        tz_top_level = e_cal_util_new_top_level ();

        iter = icalcomponent_begin_component (main_comp, ICAL_VTIMEZONE_COMPONENT);
        while ((sub_comp = icalcompiter_deref (&iter)) != NULL) {
            icalcomponent *clone;

            clone = icalcomponent_new_clone (sub_comp);
            icalcomponent_add_component (tz_top_level, clone);

            icalcompiter_next (&iter);
        }

        iter = icalcomponent_begin_component (main_comp, ICAL_VFREEBUSY_COMPONENT);
        while ((sub_comp = icalcompiter_deref (&iter)) != NULL) {
            process_free_busy_comp (attendee, sub_comp, priv->zone, tz_top_level);

            icalcompiter_next (&iter);
        }
        icalcomponent_free (tz_top_level);
    } else if (kind == ICAL_VFREEBUSY_COMPONENT) {
        process_free_busy_comp (attendee, main_comp, priv->zone, NULL);
    }

    icalcomponent_free (main_comp);

    process_callbacks (qdata);
}

/*
 * Replace all instances of from_value in string with to_value
 * In the returned newly allocated string.
*/
static gchar *
replace_string (gchar *string, const gchar *from_value, gchar *to_value)
{
    gchar *replaced;
    gchar **split_uri;

    split_uri = g_strsplit (string, from_value, 0);
    replaced = g_strjoinv (to_value, split_uri);
    g_strfreev (split_uri);

    return replaced;
}

static void start_async_read (const gchar *uri, gpointer data);

typedef struct {
    ECalClient *client;
    time_t startt;
    time_t endt;
    GSList *users;
    GSList *fb_data;
    gchar *fb_uri;
    gchar *email;
    EMeetingAttendee *attendee;
    EMeetingStoreQueueData *qdata;
    EMeetingStore *store;
} FreeBusyAsyncData;

#define USER_SUB   "%u"
#define DOMAIN_SUB "%d"

static void
client_free_busy_data_cb (ECalClient *client, const GSList *ecalcomps, FreeBusyAsyncData *fbd)
{
    const GSList *iter;

    g_return_if_fail (fbd != NULL);

    for (iter = ecalcomps; iter != NULL; iter = iter->next) {
        ECalComponent *comp = iter->data;

        if (comp)
            fbd->fb_data = g_slist_prepend (fbd->fb_data, g_object_ref (comp));
    }
}

static gboolean
freebusy_async (gpointer data)
{
    FreeBusyAsyncData *fbd = data;
    EMeetingAttendee *attendee = fbd->attendee;
    gchar *default_fb_uri = NULL;
    gchar *fburi = NULL;
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
    EMeetingStorePrivate *priv = fbd->store->priv;

    if (fbd->client) {
        guint sigid;
        /* FIXME This a workaround for getting all the free busy
         *       information for the users.  We should be able to
         *       get free busy asynchronously. */
        g_static_mutex_lock (&mutex);
        priv->num_queries++;
        sigid = g_signal_connect (fbd->client, "free-busy-data", G_CALLBACK (client_free_busy_data_cb), fbd);
        e_cal_client_get_free_busy_sync (fbd->client, fbd->startt, fbd->endt, fbd->users, NULL, NULL);
        g_signal_handler_disconnect (fbd->client, sigid);
        priv->num_queries--;
        g_static_mutex_unlock (&mutex);

        g_slist_foreach (fbd->users, (GFunc) g_free, NULL);
        g_slist_free (fbd->users);

        if (fbd->fb_data != NULL) {
            ECalComponent *comp = fbd->fb_data->data;
            gchar *comp_str;

            comp_str = e_cal_component_get_as_string (comp);
            process_free_busy (fbd->qdata, comp_str);
            g_free (comp_str);

            return TRUE;
        }
    }

    /* Look for fburl's of attendee with no free busy info on server */
    if (!e_meeting_attendee_is_set_address (attendee)) {
        process_callbacks (fbd->qdata);
        return TRUE;
    }

    /* Check for free busy info on the default server */
    default_fb_uri = g_strdup (fbd->fb_uri);
    fburi = g_strdup (e_meeting_attendee_get_fburi (attendee));

    if (fburi && !*fburi) {
        g_free (fburi);
        fburi = NULL;
    }

    if (fburi) {
        priv->num_queries++;
        start_async_read (fburi, fbd->qdata);
        g_free (fburi);
    } else if (default_fb_uri != NULL && !g_str_equal (default_fb_uri, "")) {
        gchar *tmp_fb_uri;
        gchar **split_email;

        split_email = g_strsplit (fbd->email, "@", 2);

        tmp_fb_uri = replace_string (default_fb_uri, USER_SUB, split_email[0]);
        g_free (default_fb_uri);
        default_fb_uri = replace_string (tmp_fb_uri, DOMAIN_SUB, split_email[1]);

        priv->num_queries++;
        start_async_read (default_fb_uri, fbd->qdata);
        g_free (tmp_fb_uri);
        g_strfreev (split_email);
        g_free (default_fb_uri);
    } else {
        process_callbacks (fbd->qdata);
    }

    return TRUE;
}

#undef USER_SUB
#undef DOMAIN_SUB

static gboolean
refresh_busy_periods (gpointer data)
{
    EMeetingStore *store = E_MEETING_STORE (data);
    EMeetingStorePrivate *priv;
    EMeetingAttendee *attendee = NULL;
    EMeetingStoreQueueData *qdata = NULL;
    gint i;
    GThread *thread;
    GError *error = NULL;
    FreeBusyAsyncData *fbd;

    priv = store->priv;

    /* Check to see if there are any remaining attendees in the queue */
    for (i = 0; i < priv->refresh_queue->len; i++) {
        attendee = g_ptr_array_index (priv->refresh_queue, i);
        g_return_val_if_fail (attendee != NULL, FALSE);

        qdata = g_hash_table_lookup (
            priv->refresh_data, itip_strip_mailto (
            e_meeting_attendee_get_address (attendee)));
        if (!qdata)
            continue;

        if (!qdata->refreshing)
            break;
    }

    /* The everything in the queue is being refreshed */
    if (i >= priv->refresh_queue->len) {
        priv->refresh_idle_id = 0;
        return FALSE;
    }

    /* Indicate we are trying to refresh it */
    qdata->refreshing = TRUE;

    /* We take a ref in case we get destroyed in the gui during a callback */
    g_object_ref (qdata->store);

    fbd = g_new0 (FreeBusyAsyncData, 1);
    fbd->client = priv->client;
    fbd->attendee = attendee;
    fbd->users = NULL;
    fbd->fb_data = NULL;
    fbd->qdata = qdata;
    fbd->fb_uri = priv->fb_uri;
    fbd->store = store;
    fbd->email = g_strdup (itip_strip_mailto (
        e_meeting_attendee_get_address (attendee)));

    /* Check the server for free busy data */
    if (priv->client) {
        struct icaltimetype itt;

        itt = icaltime_null_time ();
        itt.year = g_date_get_year (&qdata->start.date);
        itt.month = g_date_get_month (&qdata->start.date);
        itt.day = g_date_get_day (&qdata->start.date);
        itt.hour = qdata->start.hour;
        itt.minute = qdata->start.minute;
        fbd->startt = icaltime_as_timet_with_zone (itt, priv->zone);

        itt = icaltime_null_time ();
        itt.year = g_date_get_year (&qdata->end.date);
        itt.month = g_date_get_month (&qdata->end.date);
        itt.day = g_date_get_day (&qdata->end.date);
        itt.hour = qdata->end.hour;
        itt.minute = qdata->end.minute;
        fbd->endt = icaltime_as_timet_with_zone (itt, priv->zone);
        fbd->qdata = qdata;

        fbd->users = g_slist_append (fbd->users, g_strdup (fbd->email));

    }

    g_mutex_lock (store->priv->mutex);
    store->priv->num_threads++;
    g_mutex_unlock (store->priv->mutex);

    thread = g_thread_create ((GThreadFunc) freebusy_async, fbd, FALSE, &error);
    if (!thread) {
        /* do clean up stuff here */
        g_slist_foreach (fbd->users, (GFunc) g_free, NULL);
        g_slist_free (fbd->users);
        g_free (fbd->email);
        priv->refresh_idle_id = 0;

        g_mutex_lock (store->priv->mutex);
        store->priv->num_threads--;
        g_mutex_unlock (store->priv->mutex);

        return FALSE;
    }

    return TRUE;
}

static void
refresh_queue_add (EMeetingStore *store, gint row,
           EMeetingTime *start,
           EMeetingTime *end,
           EMeetingStoreRefreshCallback call_back,
           gpointer data)
{
    EMeetingStorePrivate *priv;
    EMeetingAttendee *attendee;
    EMeetingStoreQueueData *qdata;
    gint i;

    priv = store->priv;

    attendee = g_ptr_array_index (priv->attendees, row);
    if ((attendee == NULL) || !strcmp (itip_strip_mailto (
        e_meeting_attendee_get_address (attendee)), ""))
        return;

    /* check the queue if the attendee is already in there*/
    for (i = 0; i < priv->refresh_queue->len; i++) {
        if (attendee == g_ptr_array_index (priv->refresh_queue, i))
            return;

        if (!strcmp (e_meeting_attendee_get_address (attendee),
            e_meeting_attendee_get_address (
            g_ptr_array_index (priv->refresh_queue, i))))
            return;
    }

    g_mutex_lock (priv->mutex);
    qdata = g_hash_table_lookup (
        priv->refresh_data, itip_strip_mailto (
        e_meeting_attendee_get_address (attendee)));

    if (qdata == NULL) {
        qdata = g_new0 (EMeetingStoreQueueData, 1);

        qdata->store = store;
        qdata->attendee = attendee;
        e_meeting_attendee_clear_busy_periods (attendee);
        e_meeting_attendee_set_has_calendar_info (attendee, FALSE);

        qdata->start = *start;
        qdata->end = *end;
        qdata->string = g_string_new (NULL);
        qdata->call_backs = g_ptr_array_new ();
        qdata->data = g_ptr_array_new ();
        g_ptr_array_add (qdata->call_backs, call_back);
        g_ptr_array_add (qdata->data, data);

        g_hash_table_insert (
            priv->refresh_data, g_strdup (itip_strip_mailto (
            e_meeting_attendee_get_address (attendee))), qdata);
    } else {
        if (e_meeting_time_compare_times (start, &qdata->start) == -1)
            qdata->start = *start;
        if (e_meeting_time_compare_times (end, &qdata->end) == -1)
            qdata->end = *end;
        g_ptr_array_add (qdata->call_backs, call_back);
        g_ptr_array_add (qdata->data, data);
    }
    g_mutex_unlock (priv->mutex);

    g_object_ref (attendee);
    g_ptr_array_add (priv->refresh_queue, attendee);

    if (priv->refresh_idle_id == 0)
        priv->refresh_idle_id = g_idle_add (refresh_busy_periods, store);
}

static void
async_read (GObject *source_object, GAsyncResult *res, gpointer data)
{
    EMeetingStoreQueueData *qdata = data;
    GError *error = NULL;
    GInputStream *istream;
    gssize read;

    g_return_if_fail (source_object != NULL);
    g_return_if_fail (G_IS_INPUT_STREAM (source_object));

    istream = G_INPUT_STREAM (source_object);

    read = g_input_stream_read_finish (istream, res, &error);

    if (error || read < 0) {
        g_warning ("Read finish failed: %s", error ? error->message : "Unknown error");
        if (error)
            g_error_free (error);

        g_input_stream_close (istream, NULL, NULL);
        g_object_unref (istream);
        process_free_busy (qdata, qdata->string->str);
        return;
    }

    if (read == 0) {
        g_input_stream_close (istream, NULL, NULL);
        g_object_unref (istream);
        process_free_busy (qdata, qdata->string->str);
    } else {
        qdata->buffer[read] = '\0';
        qdata->string = g_string_append (qdata->string, qdata->buffer);

        g_input_stream_read_async (
            istream, qdata->buffer, BUF_SIZE - 1,
            G_PRIORITY_DEFAULT, NULL, async_read, qdata);
    }
}

static void
soup_authenticate (SoupSession *session,
                   SoupMessage *msg,
                   SoupAuth *auth,
                   gboolean retrying,
                   gpointer data)
{
    SoupURI *suri;
    const gchar *orig_uri;
    gboolean tried = FALSE;

    g_return_if_fail (msg != NULL);
    g_return_if_fail (auth != NULL);

    orig_uri = g_object_get_data (G_OBJECT (msg), "orig-uri");
    g_return_if_fail (orig_uri != NULL);

    suri = soup_uri_new (orig_uri);
    if (!suri)
        return;

    if (!suri->user || !*suri->user) {
        soup_uri_free (suri);
        return;
    }

    if (!retrying) {
        if (suri->password) {
            soup_auth_authenticate (auth, suri->user, suri->password);
            tried = TRUE;
        } else {
            gchar *password;

            password = e_passwords_get_password ("Calendar", orig_uri);
            if (password) {
                soup_auth_authenticate (auth, suri->user, password);
                tried = TRUE;

                memset (password, 0, strlen (password));
                g_free (password);
            }
        }
    }

    if (!tried) {
        gboolean remember = FALSE;
        gchar *password, *bold_host, *bold_user;
        GString *description;

        bold_host = g_strconcat ("<b>", suri->host, "</b>", NULL);
        bold_user = g_strconcat ("<b>", suri->user, "</b>", NULL);

        description = g_string_new ("");

        g_string_append_printf (
            description, _("Enter password to access "
            "free/busy information on server %s as user %s"),
            bold_host, bold_user);

        g_free (bold_host);
        g_free (bold_user);

        if (retrying && msg->reason_phrase && *msg->reason_phrase) {
            g_string_append (description, "\n");
            g_string_append_printf (
                description, _("Failure reason: %s"),
                msg->reason_phrase);
        }

        password = e_passwords_ask_password (
            _("Enter password"), "Calendar", orig_uri,
            description->str, E_PASSWORDS_REMEMBER_FOREVER |
            E_PASSWORDS_SECRET | E_PASSWORDS_ONLINE |
            (retrying ? E_PASSWORDS_REPROMPT : 0),
            &remember, NULL);

        g_string_free (description, TRUE);

        if (password) {
            soup_auth_authenticate (auth, suri->user, password);
            tried = TRUE;

            memset (password, 0, strlen (password));
            g_free (password);
        }
    }

    soup_uri_free (suri);
}

static void
redirect_handler (SoupMessage *msg, gpointer user_data)
{
    if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
        SoupSession *soup_session = user_data;
        SoupURI *new_uri;
        const gchar *new_loc;

        new_loc = soup_message_headers_get (msg->response_headers, "Location");
        if (!new_loc)
            return;

        new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
        if (!new_uri) {
            soup_message_set_status_full (msg,
                              SOUP_STATUS_MALFORMED,
                              "Invalid Redirect URL");
            return;
        }

        soup_message_set_uri (msg, new_uri);
        soup_session_requeue_message (soup_session, msg);

        soup_uri_free (new_uri);
    }
}

static void
soup_msg_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
{
    EMeetingStoreQueueData *qdata = user_data;

    g_return_if_fail (session != NULL);
    g_return_if_fail (msg != NULL);
    g_return_if_fail (qdata != NULL);

    if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
        qdata->string = g_string_new_len (
            msg->response_body->data,
            msg->response_body->length);
        process_free_busy (qdata, qdata->string->str);
    } else {
        g_warning (
            "Unable to access free/busy url: %s",
            msg->reason_phrase && *msg->reason_phrase ?
            msg->reason_phrase : (soup_status_get_phrase (
            msg->status_code) ? soup_status_get_phrase (
            msg->status_code) : "Unknown error"));
        process_callbacks (qdata);
    }
}

static void
download_with_libsoup (const gchar *uri, EMeetingStoreQueueData *qdata)
{
    SoupSession *session;
    SoupMessage *msg;
    EProxy *proxy;

    g_return_if_fail (uri != NULL);
    g_return_if_fail (qdata != NULL);

    msg = soup_message_new (SOUP_METHOD_GET, uri);
    if (!msg) {
        g_warning ("Unable to access free/busy url '%s'; malformed?", uri);
        process_callbacks (qdata);
        return;
    }

    g_object_set_data_full (G_OBJECT (msg), "orig-uri", g_strdup (uri), g_free);

    session = soup_session_async_new ();
    g_signal_connect (session, "authenticate", G_CALLBACK (soup_authenticate), NULL);

    proxy = e_proxy_new ();
    e_proxy_setup_proxy (proxy);

    if (e_proxy_require_proxy_for_uri (proxy, uri)) {
        SoupURI *proxy_uri;

        proxy_uri = e_proxy_peek_uri_for (proxy, uri);
        g_object_set (session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
    }

    g_object_unref (proxy);

    soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
    soup_message_add_header_handler (
        msg, "got_body", "Location",
        G_CALLBACK (redirect_handler), session);
    soup_message_headers_append (msg->request_headers, "Connection", "close");
    soup_session_queue_message (session, msg, soup_msg_ready_cb, qdata);
}

static void
start_async_read (const gchar *uri, gpointer data)
{
    EMeetingStoreQueueData *qdata = data;
    GError *error = NULL;
    GFile *file;
    GInputStream *istream;

    g_return_if_fail (uri != NULL);
    g_return_if_fail (data != NULL);

    qdata->store->priv->num_queries--;
    file = g_file_new_for_uri (uri);

    g_return_if_fail (file != NULL);

    istream = G_INPUT_STREAM (g_file_read (file, NULL, &error));

    if (error && g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
        download_with_libsoup (uri, qdata);
        g_object_unref (file);
        g_error_free (error);
        return;
    }

    if (error) {
        g_warning ("Unable to access free/busy url: %s", error->message);
        g_error_free (error);
        process_callbacks (qdata);
        g_object_unref (file);
        return;
    }

    if (!istream) {
        process_callbacks (qdata);
        g_object_unref (file);
    } else
        g_input_stream_read_async (
            istream, qdata->buffer, BUF_SIZE - 1,
            G_PRIORITY_DEFAULT, NULL, async_read, qdata);
}

void
e_meeting_store_refresh_all_busy_periods (EMeetingStore *store,
                      EMeetingTime *start,
                      EMeetingTime *end,
                      EMeetingStoreRefreshCallback call_back,
                      gpointer data)
{
    gint i;

    g_return_if_fail (E_IS_MEETING_STORE (store));

    for (i = 0; i < store->priv->attendees->len; i++)
        refresh_queue_add (store, i, start, end, call_back, data);
}

void
e_meeting_store_refresh_busy_periods (EMeetingStore *store,
                      gint row,
                      EMeetingTime *start,
                      EMeetingTime *end,
                      EMeetingStoreRefreshCallback call_back,
                      gpointer data)
{
    g_return_if_fail (E_IS_MEETING_STORE (store));

    refresh_queue_add (store, row, start, end, call_back, data);
}

guint
e_meeting_store_get_num_queries (EMeetingStore *store)
{
    g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);

    return store->priv->num_queries;
}