/* * e-meeting-store.c * * Copyright (C) 2001-2003 Ximian, Inc. * * Authors: JP Rosevear * Mike Kestner * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "calendar-config.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; ECal *client; icaltimezone *zone; char *fb_uri; EBook *ebook; GPtrArray *refresh_queue; GHashTable *refresh_data; GMutex *mutex; guint refresh_idle_id; guint callback_idle_id; guint num_threads; GAsyncQueue *async_queue; }; #define BUF_SIZE 1024 typedef struct _EMeetingStoreQueueData EMeetingStoreQueueData; struct _EMeetingStoreQueueData { EMeetingStore *store; EMeetingAttendee *attendee; gboolean refreshing; EMeetingTime start; EMeetingTime end; char buffer[BUF_SIZE]; GString *string; GPtrArray *call_backs; GPtrArray *data; }; static GObjectClass *parent_class = NULL; static void start_async_read (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, gpointer data); static void start_addressbook_server (EMeetingStore *store) { store->priv->ebook = e_book_new_system_addressbook (NULL); e_book_open (store->priv->ebook, FALSE, NULL); } static icalparameter_cutype text_to_type (const char *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 char * 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 char *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 char * 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"); } return NULL; } static gboolean text_to_boolean (const char *role) { if (!e_util_utf8_strcasecmp (role, _("Yes"))) return TRUE; else return FALSE; } static char * boolean_to_text (gboolean b) { if (b) return _("Yes"); else return _("No"); } static icalparameter_partstat text_to_partstat (const char *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 char * 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"); } return NULL; } 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 int 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, int 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) { int 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) { int 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, int col, GValue *value) { EMeetingStore *store; EMeetingAttendee *attendee; const gchar *cn; int 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) { int 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 int 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, int 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, int row, int 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 && *((char *)val)) e_meeting_attendee_set_address (attendee, g_strdup_printf ("MAILTO:%s", (char *) 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; } } static gboolean is_cell_editable (EMeetingStore *etm, int col, int row) { EMeetingStore *store; EMeetingStorePrivate *priv; EMeetingAttendee *attendee; EMeetingAttendeeEditLevel level; store = E_MEETING_STORE (etm); priv = store->priv; if (col == E_MEETING_STORE_DELTO_COL || col == E_MEETING_STORE_DELFROM_COL) return FALSE; if (row == -1) return TRUE; if (row >= priv->attendees->len) return TRUE; attendee = g_ptr_array_index (priv->attendees, row); level = e_meeting_attendee_get_edit_level (attendee); switch (level) { case E_MEETING_ATTENDEE_EDIT_FULL: return TRUE; case E_MEETING_ATTENDEE_EDIT_STATUS: return col == E_MEETING_STORE_STATUS_COL; case E_MEETING_ATTENDEE_EDIT_NONE: return FALSE; } return TRUE; } 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) { 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 ems_finalize (GObject *obj) { EMeetingStore *store = E_MEETING_STORE (obj); EMeetingStorePrivate *priv; int i; priv = store->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); if (priv->ebook != NULL) g_object_unref (priv->ebook); while (priv->refresh_queue->len > 0) refresh_queue_remove (store, 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); if (priv->callback_idle_id) g_source_remove (priv->callback_idle_id); g_free (priv->fb_uri); g_mutex_free (priv->mutex); g_async_queue_unref (priv->async_queue); g_free (priv); if (G_OBJECT_CLASS (parent_class)->finalize) (* G_OBJECT_CLASS (parent_class)->finalize) (obj); } static void ems_class_init (GObjectClass *klass) { parent_class = g_type_class_peek_parent (klass); klass->finalize = ems_finalize; } static void ems_init (EMeetingStore *store) { EMeetingStorePrivate *priv; priv = g_new0 (EMeetingStorePrivate, 1); store->priv = priv; priv->attendees = g_ptr_array_new (); priv->zone = calendar_config_get_icaltimezone (); priv->fb_uri = calendar_config_get_free_busy_template (); priv->refresh_queue = g_ptr_array_new (); priv->refresh_data = g_hash_table_new (g_str_hash, g_str_equal); priv->mutex = g_mutex_new (); priv->async_queue = g_async_queue_new (); start_addressbook_server (store); } GType e_meeting_store_get_type (void) { static GType ems_type = 0; if (!ems_type) { static const GTypeInfo ems_info = { sizeof (EMeetingStoreClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) ems_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (EMeetingStore), 0, (GInstanceInitFunc) ems_init }; static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) ems_tree_model_init, NULL, NULL }; ems_type = g_type_register_static (GTK_TYPE_LIST_STORE, "EMeetingStore", &ems_info, 0); g_type_add_interface_static (ems_type, GTK_TYPE_TREE_MODEL, &tree_model_info); } return ems_type; } GObject * e_meeting_store_new (void) { return g_object_new (E_TYPE_MEETING_STORE, NULL); } ECal * e_meeting_store_get_e_cal (EMeetingStore *store) { return store->priv->client; } void e_meeting_store_set_e_cal (EMeetingStore *store, ECal *client) { if (store->priv->client != NULL) g_object_unref (store->priv->client); if (client != NULL) g_object_ref (client); store->priv->client = client; } icaltimezone * e_meeting_store_get_zone (EMeetingStore *store) { g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL); return store->priv->zone; } void e_meeting_store_set_zone (EMeetingStore *store, icaltimezone *zone) { g_return_if_fail (E_IS_MEETING_STORE (store)); store->priv->zone = zone; } gchar * e_meeting_store_get_fb_uri (EMeetingStore *store) { g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL); return g_strdup (store->priv->fb_uri); } void e_meeting_store_set_fb_uri (EMeetingStore *store, const gchar *fb_uri) { g_return_if_fail (E_IS_MEETING_STORE (store)); g_free (store->priv->fb_uri); store->priv->fb_uri = g_strdup (fb_uri); } 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; char *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) { g_ptr_array_remove_index (store->priv->attendees, row); g_object_unref (attendee); 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); } } void e_meeting_store_remove_all_attendees (EMeetingStore *store) { gint i; GtkTreePath *path = gtk_tree_path_new (); gtk_tree_path_append_index (path, 0); for (i = 0; i < store->priv->attendees->len; i++) { EMeetingAttendee *attendee = g_ptr_array_index (store->priv->attendees, i); g_object_unref (attendee); gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), path); gtk_tree_path_next (path); } g_ptr_array_set_size (store->priv->attendees, 0); gtk_tree_path_free (path); } EMeetingAttendee * e_meeting_store_find_attendee (EMeetingStore *store, const gchar *address, gint *row) { EMeetingAttendee *attendee; int 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 char *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 char *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; } typedef struct { EMeetingStoreRefreshCallback call_back; gpointer *data; } QueueCbData; /* Process the callbacks in the main thread. Avoids widget redrawing issues. */ static gboolean process_callbacks_main_thread (void *data) { EMeetingStore *store = data; EMeetingStorePrivate *priv; QueueCbData *aqueue_data; gboolean threads_done = FALSE; priv = store->priv; g_mutex_lock (priv->mutex); if (priv->num_threads == 0) { threads_done = TRUE; priv->callback_idle_id = 0; } g_mutex_unlock (priv->mutex); while ((aqueue_data = g_async_queue_try_pop (priv->async_queue)) != NULL) { aqueue_data->call_back (aqueue_data->data); g_free (aqueue_data); } return !threads_done; } static void process_callbacks (EMeetingStoreQueueData *qdata) { EMeetingStore *store; int i; store = qdata->store; for (i = 0; i < qdata->call_backs->len; i++) { QueueCbData *aqueue_data = g_new0 (QueueCbData, 1); aqueue_data->call_back = g_ptr_array_index (qdata->call_backs, i); aqueue_data->data = g_ptr_array_index (qdata->data, i); g_async_queue_push (store->priv->async_queue, aqueue_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_async_queue_unref (store->priv->async_queue); 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; 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, char *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, 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; } typedef struct { ECal *client; time_t startt; time_t endt; GList *users; GList *fb_data; char *fb_uri; char *email; EMeetingAttendee *attendee; EMeetingStoreQueueData *qdata; } FreeBusyAsyncData; #define USER_SUB "%u" #define DOMAIN_SUB "%d" static gboolean freebusy_async (gpointer data) { FreeBusyAsyncData *fbd = data; EMeetingAttendee *attendee = fbd->attendee; gchar *default_fb_uri; static GStaticMutex mutex = G_STATIC_MUTEX_INIT; if (fbd->client) { /* FIXME this a work around for getting all th free busy information for the users we should be able to get free busy asynchronously */ g_static_mutex_lock (&mutex); e_cal_get_free_busy (fbd->client, fbd->users, fbd->startt, fbd->endt, &(fbd->fb_data), NULL); g_static_mutex_unlock (&mutex); g_list_foreach (fbd->users, (GFunc)g_free, NULL); g_list_free (fbd->users); if (fbd->fb_data != NULL) { ECalComponent *comp = fbd->fb_data->data; char *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); if (default_fb_uri != NULL && !g_str_equal (default_fb_uri, "")) { GnomeVFSAsyncHandle *handle; 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]); gnome_vfs_async_open (&handle, default_fb_uri, GNOME_VFS_OPEN_READ, GNOME_VFS_PRIORITY_DEFAULT, start_async_read, 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; int 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_assert (attendee != NULL); 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->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_year (&qdata->start.date); itt.month = g_date_month (&qdata->start.date); itt.day = g_date_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_year (&qdata->end.date); itt.month = g_date_month (&qdata->end.date); itt.day = g_date_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_list_append (fbd->users, g_strdup (fbd->email)); } g_async_queue_ref (priv->async_queue); 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_list_foreach (fbd->users, (GFunc)g_free, NULL); g_list_free (fbd->users); g_free (fbd->email); priv->refresh_idle_id = 0; g_async_queue_unref (priv->async_queue); g_mutex_lock (store->priv->mutex); store->priv->num_threads--; g_mutex_unlock (store->priv->mutex); return FALSE; } else if (priv->callback_idle_id == 0) { priv->callback_idle_id = g_idle_add (process_callbacks_main_thread, store); } return TRUE; } static void refresh_queue_add (EMeetingStore *store, int row, EMeetingTime *start, EMeetingTime *end, EMeetingStoreRefreshCallback call_back, gpointer data) { EMeetingStorePrivate *priv; EMeetingAttendee *attendee; EMeetingStoreQueueData *qdata; int 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, (void *)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_close (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, gpointer data) { EMeetingStoreQueueData *qdata = data; process_free_busy (qdata, qdata->string->str); } static void async_read (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, gpointer buffer, GnomeVFSFileSize requested, GnomeVFSFileSize read, gpointer data) { EMeetingStoreQueueData *qdata = data; GnomeVFSFileSize buf_size = BUF_SIZE - 1; if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) { gnome_vfs_async_close (handle, async_close, qdata); return; } ((char *)buffer)[read] = '\0'; qdata->string = g_string_append (qdata->string, buffer); if (result == GNOME_VFS_ERROR_EOF) { gnome_vfs_async_close (handle, async_close, qdata); return; } gnome_vfs_async_read (handle, qdata->buffer, buf_size, async_read, qdata); } static void start_async_read (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, gpointer data) { EMeetingStoreQueueData *qdata = data; GnomeVFSFileSize buf_size = BUF_SIZE - 1; if (result != GNOME_VFS_OK) { g_warning ("Unable to access free/busy url: %s", gnome_vfs_result_to_string (result)); process_callbacks (qdata); return; } gnome_vfs_async_read (handle, qdata->buffer, buf_size, async_read, qdata); } void e_meeting_store_refresh_all_busy_periods (EMeetingStore *store, EMeetingTime *start, EMeetingTime *end, EMeetingStoreRefreshCallback call_back, gpointer data) { int 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, int 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); }