/* sunone-invitation-list-model.c * * Copyright (C) 2002-2005 Sun Microsystems, Inc * * AUTHORS * Jack Jia * Harry Lu * Alfred Peng * Rodrigo Moya * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "lib/sunone-util.h" #include "calendar/cal-backend-wcap-events.h" #include "sunone-invitation-list-model.h" #include "sunone-component.h" #include "sunone-offline-listener.h" /* poll every 60 minutes */ #define POLL_INTERVAL 60*60*1000 #define ROW_VALID(model, row) (row >= 0 && row < g_hash_table_size (model->priv->invitations)) struct _SunOneInvitationListModelPrivate { SunOneConnection *so_cnc; GHashTable *invitations; GList *invitations_list; EConfigListener *config; gint timeout_id; gint stamp; char *uri; char *calid; }; static void sunone_invitation_list_model_class_init (SunOneInvitationListModelClass *klass); static void sunone_invitation_list_model_init (SunOneInvitationListModel *object); static void sunone_invitation_list_model_dispose (GObject *object); static void sunone_invitation_list_model_finalize (GObject *object); static GObjectClass *parent_class = NULL; typedef struct { ECalComponent *comp; int count; gboolean exists; } SunOneInvitationData; const char * sunone_invitation_list_model_get_uri (SunOneInvitationListModel *model) { SunOneInvitationListModelPrivate *priv = model->priv; g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), NULL); return priv->uri; } static void sunone_invitation_list_model_class_init (SunOneInvitationListModelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_ref (G_TYPE_OBJECT); object_class->dispose = sunone_invitation_list_model_dispose; object_class->finalize = sunone_invitation_list_model_finalize; } static void key_changed_cb (EConfigListener *listener, const char *key, void *data) { SunOneInvitationListModel *model = SUNONE_INVITATION_LIST_MODEL (data); SunOneInvitationListModelPrivate *priv = model->priv; char *location; location = e_config_listener_get_string_with_default (priv->config, "/apps/evolution/calendar/display/timezone", "UTC", NULL); model->zone = icaltimezone_get_builtin_timezone (location); g_free (location); sunone_invitation_list_model_poll_cb (model); } static void sunone_invitation_list_model_init (SunOneInvitationListModel *object) { SunOneInvitationListModel *model = SUNONE_INVITATION_LIST_MODEL (object); SunOneInvitationListModelPrivate *priv; char *location; model->priv = g_new0 (SunOneInvitationListModelPrivate, 1); priv = model->priv; priv->uri = NULL; priv->calid = NULL; priv->stamp = g_random_int (); priv->invitations = g_hash_table_new (g_str_hash, g_str_equal); priv->invitations_list = NULL; priv->config = e_config_listener_new (); g_signal_connect (G_OBJECT (priv->config), "key_changed", G_CALLBACK (key_changed_cb), model); priv->timeout_id = -1; model->account = NULL; location = e_config_listener_get_string_with_default (priv->config, "/apps/evolution/calendar/display/timezone", "UTC", NULL); model->zone = icaltimezone_get_builtin_timezone (location); g_free (location); } static void sunone_invitation_list_model_dispose (GObject *object) { SunOneInvitationListModel *model = SUNONE_INVITATION_LIST_MODEL (object); SunOneInvitationListModelPrivate *priv = model->priv; g_return_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model)); if (model->account) { g_object_unref (G_OBJECT (model->account)); model->account = NULL; } if (model->props) { sunone_connection_free_calprops (model->props); model->props = NULL; } if (model->zone) model->zone = NULL; if (priv) { if (priv->uri) { g_free (priv->uri); priv->uri = NULL; } if (priv->calid) { g_free (priv->calid); priv->calid = NULL; } if (priv->config) { g_object_unref (G_OBJECT (priv->config)); priv->config = NULL; } if (priv->timeout_id != -1) { g_source_remove (priv->timeout_id); priv->timeout_id = -1; } if (priv->invitations) { /* FIXME, destroy elements */ g_hash_table_destroy (priv->invitations); priv->invitations = NULL; } if (priv->invitations_list) { g_list_free (priv->invitations_list); priv->invitations_list = NULL; } g_free (priv); model->priv = NULL; } if (G_OBJECT_CLASS (parent_class) ->dispose) G_OBJECT_CLASS (parent_class) ->dispose (object); } static void sunone_invitation_list_model_finalize (GObject *object) { if (G_OBJECT_CLASS (parent_class) ->dispose) G_OBJECT_CLASS (parent_class) ->finalize (object); } static GtkTreeModelFlags get_flags (GtkTreeModel *model) { g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (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 (SUNONE_IS_INVITATION_LIST_MODEL (model), 0); return LAST_COL; } static GType get_column_type (GtkTreeModel *model, int col) { g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), G_TYPE_INVALID); switch (col) { case START_COL: case END_COL: case LOCATION_COL: case SUMMARY_COL: case ORGANIZER_COL: case STATUS_COL: return G_TYPE_STRING; default: return G_TYPE_INVALID; } } static gboolean get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) { SunOneInvitationListModelPrivate *priv = SUNONE_INVITATION_LIST_MODEL (model)->priv; int row; g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (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 (SUNONE_INVITATION_LIST_MODEL (model), row)) return FALSE; iter->stamp = priv->stamp; iter->user_data = GINT_TO_POINTER (row); return TRUE; } static GtkTreePath * get_path (GtkTreeModel *model, GtkTreeIter *iter) { SunOneInvitationListModelPrivate *priv = SUNONE_INVITATION_LIST_MODEL (model)->priv; GtkTreePath *result; int row; g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), NULL); g_return_val_if_fail (iter->stamp == priv->stamp, NULL); row = GPOINTER_TO_INT (iter->user_data); g_return_val_if_fail (ROW_VALID (SUNONE_INVITATION_LIST_MODEL (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) { SunOneInvitationListModel *list_model; int row; SunOneInvitationData *id; ECalComponentDateTime dt; ECalComponentText text; ECalComponentOrganizer organizer; GSList *attendees, *l; icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION; struct tm t; const char *string; static char buffer[512]; g_return_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model)); g_return_if_fail (col >= 0 && col < LAST_COL); row = GPOINTER_TO_INT (iter->user_data); list_model = SUNONE_INVITATION_LIST_MODEL (model); g_return_if_fail (iter->stamp == list_model->priv->stamp); g_return_if_fail (ROW_VALID (list_model, row)); id = g_list_nth_data (list_model->priv->invitations_list, row); switch (col) { case START_COL: g_value_init (value, G_TYPE_STRING); e_cal_component_get_dtstart (id->comp, &dt); if (dt.value) { icaltimezone_convert_time (dt.value, icaltimezone_get_utc_timezone (), list_model->zone); t = icaltimetype_to_tm (dt.value); e_time_format_date_and_time (&t, FALSE, !dt.value->is_date, FALSE, buffer, 512); } else { buffer[0] ='\0'; } e_cal_component_free_datetime (&dt); g_value_set_string (value, buffer); break; case END_COL: g_value_init (value, G_TYPE_STRING); e_cal_component_get_dtend (id->comp, &dt); if (dt.value) { icaltimezone_convert_time (dt.value, icaltimezone_get_utc_timezone (), list_model->zone); t = icaltimetype_to_tm (dt.value); e_time_format_date_and_time (&t, FALSE, !dt.value->is_date, FALSE, buffer, 512); } else { e_cal_component_free_datetime (&dt); e_cal_component_get_dtstart (id->comp, &dt); if (dt.value && dt.value->is_date) { t = icaltimetype_to_tm (dt.value); e_time_format_date_and_time (&t, FALSE, !dt.value->is_date, FALSE, buffer, 512); } else { buffer[0] ='\0'; } } e_cal_component_free_datetime (&dt); g_value_set_string (value, buffer); break; case LOCATION_COL: g_value_init (value, G_TYPE_STRING); e_cal_component_get_location (id->comp, &string); g_value_set_string (value, string ? string: ""); break; case SUMMARY_COL: g_value_init (value, G_TYPE_STRING); e_cal_component_get_summary (id->comp, &text); if (id->count == 1) g_value_set_string (value, text.value ? text.value : _("Untitled Appointment")); else { g_snprintf (buffer, 512, "%s (%d)", text.value ? (char *)text.value : _("Untitled Appointment"), id->count); g_value_set_string (value, buffer); } break; case ORGANIZER_COL: g_value_init (value, G_TYPE_STRING); e_cal_component_get_organizer (id->comp, &organizer); g_value_set_string (value, organizer.value ? organizer.value : ""); break; case STATUS_COL: g_value_init (value, G_TYPE_STRING); e_cal_component_get_attendee_list (id->comp, &attendees); for (l = attendees; l != NULL; l = l->next) { ECalComponentAttendee *a = l->data; const char *attendee = a->value; if (!strncasecmp ("mailto:", attendee, 7)) { attendee = attendee + 7; } if (!strcasecmp (list_model->priv->calid, attendee) || !strcasecmp (sunone_account_get_email (list_model->account), attendee)) status = a->status; } e_cal_component_free_attendee_list (attendees); switch (status) { case ICAL_PARTSTAT_NEEDSACTION: string = _("Needs Action"); break; case ICAL_PARTSTAT_ACCEPTED: string = _("Accepted"); break; case ICAL_PARTSTAT_DECLINED: string = _("Declined"); break; case ICAL_PARTSTAT_TENTATIVE: string = _("Tentative"); break; default: string = _("N/A"); } g_value_set_string (value, string); break; } } static gboolean iter_next (GtkTreeModel *model, GtkTreeIter *iter) { int row; g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), FALSE); g_return_val_if_fail (iter->stamp == SUNONE_INVITATION_LIST_MODEL (model)->priv->stamp, FALSE); row = GPOINTER_TO_INT (iter->user_data) + 1; iter->user_data = GINT_TO_POINTER (row); return ROW_VALID (SUNONE_INVITATION_LIST_MODEL (model), row); } static gboolean iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent) { SunOneInvitationListModel *list_model; g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), FALSE); list_model = SUNONE_INVITATION_LIST_MODEL (model); if (parent || g_hash_table_size (list_model->priv->invitations) <= 0) return FALSE; iter->stamp = list_model->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 (SUNONE_IS_INVITATION_LIST_MODEL (model), -1); if (!iter) return g_hash_table_size (SUNONE_INVITATION_LIST_MODEL (model)->priv->invitations); g_return_val_if_fail (iter->stamp == SUNONE_INVITATION_LIST_MODEL (model)->priv->stamp, -1); return 0; } static gboolean iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent, int n) { g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), FALSE); if (parent || !ROW_VALID (SUNONE_INVITATION_LIST_MODEL (model), n)) return FALSE; iter->stamp = SUNONE_INVITATION_LIST_MODEL (model)->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 _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; } GType sunone_invitation_list_model_get_type (void) { static GType the_type = 0; if (!the_type) { static const GTypeInfo the_info = { sizeof (SunOneInvitationListModelClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) sunone_invitation_list_model_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (SunOneInvitationListModel), 0, (GInstanceInitFunc) sunone_invitation_list_model_init }; static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) _tree_model_init, NULL, NULL }; the_type = g_type_register_static (GTK_TYPE_LIST_STORE, "SunOneInvitationListModel", &the_info, 0); g_type_add_interface_static (the_type, GTK_TYPE_TREE_MODEL, &tree_model_info); } return the_type; } gboolean sunone_invitation_list_model_poll_cb (gpointer data) { SunOneInvitationListModel *model = SUNONE_INVITATION_LIST_MODEL (data); SunOneInvitationListModelPrivate *priv = model->priv; icalcomponent *icalcomp, *subcomp; icalcomponent_kind kind; GList *removals = NULL, *l; guint error_code; int state; SunOneConnection *cnc; g_return_val_if_fail (SUNONE_IS_INVITATION_LIST_MODEL (model), FALSE); sunone_account_is_offline (model->account, &state); /* if we are offline, clear invitation list*/ if (state != ONLINE_MODE) goto removal; cnc = sunone_account_get_connection (model->account); /* Make sure we have permissions to view to items */ if (model->props) sunone_connection_free_calprops (model->props); model->props = sunone_connection_get_calprops (cnc, priv->calid, FALSE); if (!model->props) goto removal; if (!sunone_util_has_permissions (model->props, sunone_connection_get_user (cnc), SUNONE_ACE_CONTEXT_CALENDAR_COMPONENTS, SUNONE_ACE_PERMISSION_READ | SUNONE_ACE_PERMISSION_REPLY)) goto removal; /* Now fetch the invitations */ error_code = sunone_connection_fetchcomponents_by_lastmod (cnc, priv->calid, icaltime_null_time (), icaltime_null_time (), TYPE_EVENT, REQUEST_NEEDS_ACTION, &icalcomp, NULL); if (!SUNONE_ERROR_IS_SUCCESSFUL (error_code)) return TRUE; subcomp = icalcomponent_get_first_component (icalcomp, ICAL_ANY_COMPONENT); while (subcomp) { kind = icalcomponent_isa (subcomp); if (kind == ICAL_VEVENT_COMPONENT || kind == ICAL_VTODO_COMPONENT) { SunOneInvitationData *id; ECalComponent *comp; const char *uid; comp = e_cal_component_new (); e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp)); e_cal_component_get_uid (comp, &uid); id = g_hash_table_lookup (priv->invitations, uid); if (id) { if (id->exists) { id->count++; g_object_unref (G_OBJECT (comp)); } else { int row; GtkTreePath *path; GtkTreeIter iter; id->count = 1; id->exists = TRUE; g_object_unref (G_OBJECT (id->comp)); id->comp = comp; row = g_list_index (priv->invitations_list, id); path = gtk_tree_path_new (); gtk_tree_path_append_index (path, row); get_iter (GTK_TREE_MODEL (model), &iter, path); gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); } } else { GtkTreePath *path; GtkTreeIter iter; id = g_new0 (SunOneInvitationData, 1); id->comp = comp; id->count = 1; id->exists = TRUE; g_hash_table_insert (priv->invitations, g_strdup (uid), id); priv->invitations_list = g_list_append (priv->invitations_list, id); path = gtk_tree_path_new (); gtk_tree_path_append_index (path, g_hash_table_size (priv->invitations) - 1); get_iter (GTK_TREE_MODEL (model), &iter, path); gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); } } subcomp = icalcomponent_get_next_component (icalcomp, ICAL_ANY_COMPONENT); } icalcomponent_free (icalcomp); removal: for (l = priv->invitations_list; l != NULL; l = l->next) { SunOneInvitationData *id = l->data; if (!id->exists) { gpointer orig_key, orig_value; const char *uid; removals = g_list_append (removals, l); e_cal_component_get_uid (id->comp, &uid); if (g_hash_table_lookup_extended (priv->invitations, uid, &orig_key, &orig_value)) { id = orig_value; g_hash_table_remove (priv->invitations, uid); g_free (orig_key); } } else { id->exists = FALSE; } } removals = g_list_reverse (removals); for (l = removals; l != NULL; l = l->next) { SunOneInvitationData *id; int row; GtkTreePath *path; row = g_list_position (priv->invitations_list, l->data); priv->invitations_list = g_list_remove_link (priv->invitations_list, l->data); id = ((GList *)(l->data))->data; g_object_unref (G_OBJECT (id->comp)); g_free (id); g_list_free (l->data); path = gtk_tree_path_new (); gtk_tree_path_append_index (path, row); gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); gtk_tree_path_free (path); } g_list_free (removals); return TRUE; } static gboolean first_poll_cb (gpointer data) { SunOneInvitationListModel *model = SUNONE_INVITATION_LIST_MODEL (data); SunOneInvitationListModelPrivate *priv = model->priv; sunone_invitation_list_model_poll_cb (model); priv->timeout_id = g_timeout_add (POLL_INTERVAL, sunone_invitation_list_model_poll_cb, model); return FALSE; } void sunone_invitation_list_model_set_value (SunOneInvitationListModel *model, int col, int row, const char *val) { SunOneInvitationListModelPrivate *priv = model->priv; SunOneInvitationData *id; icalcomponent *icalcomp, *clonecomp; icalproperty *prop; SunOneConnection *cnc; CalBackendWCAP *wcap; char *key; cnc = sunone_account_get_connection (model->account); id = g_list_nth_data (priv->invitations_list, row); switch (col) { case STATUS_COL: icalcomp = e_cal_component_get_icalcomponent (id->comp); for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY); prop != NULL; prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) { icalvalue *value; icalparameter *param; const char *attendee; icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION; value = icalproperty_get_value (prop); if (!value) continue; attendee = icalvalue_get_string (value); if (!strncasecmp ("mailto:", attendee, 7)) { attendee = attendee + 7; } if (!strcasecmp (priv->calid, attendee) || !strcasecmp (sunone_account_get_email (model->account), attendee)) { if (!strcmp (val, _("Needs Action"))) status = ICAL_PARTSTAT_NEEDSACTION; else if (!strcmp (val, _("Accepted"))) status = ICAL_PARTSTAT_ACCEPTED; else if (!strcmp (val, _("Declined"))) status = ICAL_PARTSTAT_DECLINED; else if (!strcmp (val, _("Tentative"))) status = ICAL_PARTSTAT_TENTATIVE; icalproperty_remove_parameter (prop, ICAL_PARTSTAT_PARAMETER); param = icalparameter_new_partstat (status); icalproperty_add_parameter (prop, param); e_cal_component_rescan (id->comp); } } clonecomp = icalcomponent_new_clone(icalcomp); key = g_strconcat (priv->calid, ":calendar", NULL); wcap = sunone_connection_get_wcap (cnc, key); g_free (key); if (wcap) cal_backend_wcap_events_update_objects (E_CAL_BACKEND_SYNC (wcap), NULL, icalcomponent_as_ical_string (clonecomp), CALOBJ_MOD_ALL, NULL, NULL); icalcomponent_free (clonecomp); } } SunOneInvitationListModel * sunone_invitation_list_model_new (const char *uristr) { SunOneInvitationListModelPrivate *priv; SunOneInvitationListModel *model; SunOneAccount *account; g_return_val_if_fail (uristr != NULL, NULL); account = sunone_component_get_account_from_uri (global_sunone_component, uristr); if (!account) { return NULL; } model = g_object_new (SUNONE_TYPE_INVITATION_LIST_MODEL, NULL); priv = model->priv; /* Get a connection to use for polling for invites */ priv->uri = g_strdup (uristr); priv->calid = sunone_util_get_calid_from_uri (uristr); if (!priv->calid) { g_object_unref (G_OBJECT (model)); return NULL; } model->account = account; g_idle_add (first_poll_cb, model); return model; } ECalComponent * sunone_invitation_list_model_get_comp (SunOneInvitationListModel *model, int row) { SunOneInvitationListModelPrivate *priv = model->priv; SunOneInvitationData *id; id = g_list_nth_data (priv->invitations_list, row); if (!id) return NULL; return id->comp; }