/*
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* 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, see .
*
* Authors:
* Mark McLoughlin
* William Jon McCann
* Martin Grimme
* Christian Kellner
*/
#include
#include "calendar-client.h"
#include
#include
#define HANDLE_LIBICAL_MEMORY
#ifdef HAVE_EDS
#include
#include "calendar-sources.h"
#endif
#undef CALENDAR_ENABLE_DEBUG
#include "calendar-debug.h"
#ifndef _
#define _(x) gettext(x)
#endif
#ifndef N_
#define N_(x) x
#endif
#ifdef HAVE_EDS
typedef struct _CalendarClientQuery CalendarClientQuery;
typedef struct _CalendarClientSource CalendarClientSource;
struct _CalendarClientQuery
{
ECalClientView *view;
GHashTable *events;
};
struct _CalendarClientSource
{
CalendarClient *client;
ECalClient *source;
CalendarClientQuery completed_query;
CalendarClientQuery in_progress_query;
guint changed_signal_id;
guint query_completed : 1;
guint query_in_progress : 1;
};
struct _CalendarClientPrivate
{
CalendarSources *calendar_sources;
GSList *appointment_sources;
GSList *task_sources;
ICalTimezone *zone;
guint zone_listener;
GSettings *calendar_settings;
guint day;
guint month;
guint year;
};
static void calendar_client_finalize (GObject *object);
static void calendar_client_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void calendar_client_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static GSList *calendar_client_update_sources_list (CalendarClient *client,
GSList *sources,
GList *esources,
guint changed_signal_id);
static void calendar_client_appointment_sources_changed (CalendarClient *client);
static void calendar_client_task_sources_changed (CalendarClient *client);
static void calendar_client_stop_query (CalendarClient *client,
CalendarClientSource *source,
CalendarClientQuery *query);
static void calendar_client_start_query (CalendarClient *client,
CalendarClientSource *source,
const char *query);
static void calendar_client_source_finalize (CalendarClientSource *source);
static void calendar_client_query_finalize (CalendarClientQuery *query);
static void
calendar_client_update_appointments (CalendarClient *client);
static void
calendar_client_update_tasks (CalendarClient *client);
enum
{
PROP_O,
PROP_DAY,
PROP_MONTH,
PROP_YEAR
};
enum
{
APPOINTMENTS_CHANGED,
TASKS_CHANGED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0, };
G_DEFINE_TYPE_WITH_PRIVATE (CalendarClient, calendar_client, G_TYPE_OBJECT)
static void
calendar_client_class_init (CalendarClientClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->finalize = calendar_client_finalize;
gobject_class->set_property = calendar_client_set_property;
gobject_class->get_property = calendar_client_get_property;
g_object_class_install_property (gobject_class,
PROP_DAY,
g_param_spec_uint ("day",
"Day",
"The currently monitored day between 1 and 31 (0 denotes unset)",
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_MONTH,
g_param_spec_uint ("month",
"Month",
"The currently monitored month between 0 and 11",
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_YEAR,
g_param_spec_uint ("year",
"Year",
"The currently monitored year",
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
signals [APPOINTMENTS_CHANGED] =
g_signal_new ("appointments-changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals [TASKS_CHANGED] =
g_signal_new ("tasks-changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
}
/* Timezone code adapted from evolution/calendar/gui/calendar-config.c */
/* The current timezone, e.g. "Europe/London". It may be NULL, in which case
you should assume UTC. */
static gchar *
calendar_client_config_get_timezone (GSettings *calendar_settings)
{
return g_settings_get_string (calendar_settings, "timezone");
}
static ICalTimezone *
calendar_client_config_get_icaltimezone (CalendarClient *client)
{
gchar *location = NULL;
ICalTimezone *zone = NULL;
if (client->priv->calendar_settings != NULL)
location = calendar_client_config_get_timezone (client->priv->calendar_settings);
if (!location) {
/* MATE panel doesn't store timezone in GSettings
* Since libical timezone lookup often fails, just use UTC
* The display code will handle local time conversion */
return i_cal_timezone_get_utc_timezone ();
}
zone = i_cal_timezone_get_builtin_timezone (location);
g_free (location);
return zone;
}
static void
calendar_client_set_timezone (CalendarClient *client)
{
GList *list, *link;
client->priv->zone = calendar_client_config_get_icaltimezone (client);
list = calendar_sources_get_appointment_clients (client->priv->calendar_sources);
for (link = list; link != NULL; link = g_list_next (link))
{
ECalClient *cal = E_CAL_CLIENT (link->data);
e_cal_client_set_default_timezone (cal, client->priv->zone);
}
g_list_free (list);
}
static void
calendar_client_timezone_changed_cb (GSettings *calendar_settings,
const gchar *key,
CalendarClient *client)
{
calendar_client_set_timezone (client);
}
static void
load_calendars (CalendarClient *client,
CalendarEventType type)
{
GSList *l, *clients;
switch (type)
{
case CALENDAR_EVENT_APPOINTMENT:
clients = client->priv->appointment_sources;
break;
case CALENDAR_EVENT_TASK:
clients = client->priv->task_sources;
break;
case CALENDAR_EVENT_ALL:
default:
g_assert_not_reached ();
}
for (l = clients; l != NULL; l = l->next)
{
if (type == CALENDAR_EVENT_APPOINTMENT)
calendar_client_update_appointments (client);
else if (type == CALENDAR_EVENT_TASK)
calendar_client_update_tasks (client);
}
}
static void
calendar_client_init (CalendarClient *client)
{
GList *list;
client->priv = calendar_client_get_instance_private (client);
client->priv->calendar_sources = calendar_sources_get ();
list = calendar_sources_get_appointment_clients (client->priv->calendar_sources);
client->priv->appointment_sources =
calendar_client_update_sources_list (client, NULL, list, signals [APPOINTMENTS_CHANGED]);
g_list_free (list);
list = calendar_sources_get_task_clients (client->priv->calendar_sources);
client->priv->task_sources = calendar_client_update_sources_list (client, NULL, list, signals [TASKS_CHANGED]);
g_list_free (list);
/* set the timezone before loading the clients */
calendar_client_set_timezone (client);
load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
load_calendars (client, CALENDAR_EVENT_TASK);
g_signal_connect_swapped (client->priv->calendar_sources,
"appointment-sources-changed",
G_CALLBACK (calendar_client_appointment_sources_changed),
client);
g_signal_connect_swapped (client->priv->calendar_sources,
"task-sources-changed",
G_CALLBACK (calendar_client_task_sources_changed),
client);
if (client->priv->calendar_settings != NULL)
client->priv->zone_listener = g_signal_connect (client->priv->calendar_settings,
"changed::timezone",
G_CALLBACK (calendar_client_timezone_changed_cb),
client);
client->priv->day = G_MAXUINT;
client->priv->month = G_MAXUINT;
client->priv->year = G_MAXUINT;
}
static void
calendar_client_finalize (GObject *object)
{
CalendarClient *client = CALENDAR_CLIENT (object);
GSList *l;
if (client->priv->zone_listener)
{
g_signal_handler_disconnect (client->priv->calendar_settings,
client->priv->zone_listener);
client->priv->zone_listener = 0;
}
if (client->priv->calendar_settings)
g_object_unref (client->priv->calendar_settings);
client->priv->calendar_settings = NULL;
for (l = client->priv->appointment_sources; l; l = l->next)
{
calendar_client_source_finalize (l->data);
g_free (l->data);
}
g_slist_free (client->priv->appointment_sources);
client->priv->appointment_sources = NULL;
for (l = client->priv->task_sources; l; l = l->next)
{
calendar_client_source_finalize (l->data);
g_free (l->data);
}
g_slist_free (client->priv->task_sources);
client->priv->task_sources = NULL;
if (client->priv->calendar_sources)
g_object_unref (client->priv->calendar_sources);
client->priv->calendar_sources = NULL;
G_OBJECT_CLASS (calendar_client_parent_class)->finalize (object);
}
static void
calendar_client_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CalendarClient *client = CALENDAR_CLIENT (object);
switch (prop_id)
{
case PROP_DAY:
calendar_client_select_day (client, g_value_get_uint (value));
break;
case PROP_MONTH:
calendar_client_select_month (client,
g_value_get_uint (value),
client->priv->year);
break;
case PROP_YEAR:
calendar_client_select_month (client,
client->priv->month,
g_value_get_uint (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
calendar_client_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CalendarClient *client = CALENDAR_CLIENT (object);
switch (prop_id)
{
case PROP_DAY:
g_value_set_uint (value, client->priv->day);
break;
case PROP_MONTH:
g_value_set_uint (value, client->priv->month);
break;
case PROP_YEAR:
g_value_set_uint (value, client->priv->year);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
CalendarClient *
calendar_client_new (GSettings *settings)
{
CalendarClient *client = g_object_new (CALENDAR_TYPE_CLIENT, NULL);
/* Use the provided MATE panel settings instead of Evolution settings */
if (settings) {
client->priv->calendar_settings = g_object_ref (settings);
} else {
/* Fallback to Evolution calendar settings if available */
GSettingsSchemaSource *schema_source = g_settings_schema_source_get_default();
const gchar *evolution_calendar_schema = "org.gnome.evolution.calendar";
if (g_settings_schema_source_lookup (schema_source, evolution_calendar_schema, FALSE))
client->priv->calendar_settings = g_settings_new (evolution_calendar_schema);
}
return client;
}
/* @day and @month can happily be out of range as
* mktime() will normalize them correctly. From mktime(3):
*
* "If structure members are outside their legal interval,
* they will be normalized (so that, e.g., 40 October is
* changed into 9 November)."
*
* "What?", you say, "Something useful in libc?"
*/
static inline time_t
make_time_for_day_begin (int day,
int month,
int year)
{
struct tm localtime_tm = { 0, };
localtime_tm.tm_mday = day;
localtime_tm.tm_mon = month;
localtime_tm.tm_year = year - 1900;
localtime_tm.tm_isdst = -1;
return mktime (&localtime_tm);
}
static inline char *
make_isodate_for_day_begin (int day,
int month,
int year)
{
time_t utctime;
utctime = make_time_for_day_begin (day, month, year);
return utctime != -1 ? isodate_from_time_t (utctime) : NULL;
}
static time_t
get_time_from_property (ICalComponent *icomp,
ICalPropertyKind prop_kind,
ICalTime * (* get_prop_func) (ICalProperty *prop),
ICalTimezone *default_zone)
{
ICalProperty *prop;
ICalTime *ical_time;
ICalParameter *param;
ICalTimezone *timezone;
time_t retval;
prop = i_cal_component_get_first_property (icomp, prop_kind);
if (!prop)
return 0;
param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
ical_time = get_prop_func (prop);
g_object_unref (prop);
if (param)
{
const char *tzid;
tzid = i_cal_parameter_get_tzid (param);
timezone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
g_object_unref (param);
/* If timezone lookup failed, fall back to default zone */
if (!timezone) {
timezone = default_zone;
}
}
else if (i_cal_time_is_utc (ical_time))
{
timezone = i_cal_timezone_get_utc_timezone ();
}
else
{
timezone = default_zone;
}
retval = i_cal_time_as_timet_with_zone (ical_time, timezone);
g_object_unref (ical_time);
return retval;
}
static char *
get_component_uid (ICalComponent *component)
{
return g_strdup (i_cal_component_get_uid (component));
}
static char *
get_component_rid (ICalComponent *component)
{
ICalProperty *prop;
ICalTime *time;
char *rid;
prop = i_cal_component_get_first_property (component, I_CAL_RECURRENCEID_PROPERTY);
if (!prop)
return NULL;
time = i_cal_property_get_recurrenceid (prop);
g_object_unref (prop);
if (!i_cal_time_is_valid_time (time) || i_cal_time_is_null_time (time))
{
g_object_unref (time);
return NULL;
}
rid = g_strdup (i_cal_time_as_ical_string (time));
g_object_unref (time);
return rid;
}
static char *
get_component_summary (ICalComponent *component)
{
ICalProperty *prop;
char *summary;
prop = i_cal_component_get_first_property (component, I_CAL_SUMMARY_PROPERTY);
if (!prop)
return NULL;
summary = g_strdup (i_cal_property_get_summary (prop));
g_object_unref (prop);
return summary;
}
static char *
get_component_description (ICalComponent *component)
{
ICalProperty *prop;
char *description;
prop = i_cal_component_get_first_property (component, I_CAL_DESCRIPTION_PROPERTY);
if (!prop)
return NULL;
description = g_strdup (i_cal_property_get_description (prop));
g_object_unref (prop);
return description;
}
static inline time_t
get_component_start_time (ICalComponent *component,
ICalTimezone *default_zone)
{
return get_time_from_property (component,
I_CAL_DTSTART_PROPERTY,
i_cal_property_get_dtstart,
default_zone);
}
static inline time_t
get_component_end_time (ICalComponent *component,
ICalTimezone *default_zone)
{
return get_time_from_property (component,
I_CAL_DTEND_PROPERTY,
i_cal_property_get_dtend,
default_zone);
}
static gboolean
get_component_is_all_day (ICalComponent *component,
time_t start_time,
ICalTimezone *default_zone)
{
ICalTime *dtstart;
struct tm *start_tm;
time_t end_time;
ICalProperty *prop;
ICalDuration *duration;
gboolean is_all_day;
dtstart = i_cal_component_get_dtstart (component);
if (dtstart && i_cal_time_is_date (dtstart))
{
g_object_unref (dtstart);
return TRUE;
}
g_object_unref (dtstart);
start_tm = gmtime (&start_time);
if (start_tm->tm_sec != 0 ||
start_tm->tm_min != 0 ||
start_tm->tm_hour != 0)
return FALSE;
if ((end_time = get_component_end_time (component, default_zone)))
return (end_time - start_time) % 86400 == 0;
prop = i_cal_component_get_first_property (component, I_CAL_DURATION_PROPERTY);
if (!prop)
return FALSE;
duration = i_cal_property_get_duration (prop);
g_object_unref (prop);
is_all_day = i_cal_duration_as_int (duration) % 86400 == 0;
g_object_unref (duration);
return is_all_day;
}
static inline time_t
get_component_due_time (ICalComponent *component,
ICalTimezone *default_zone)
{
return get_time_from_property (component,
I_CAL_DUE_PROPERTY,
i_cal_property_get_due,
default_zone);
}
static guint
get_component_percent_complete (ICalComponent *component)
{
ICalPropertyStatus status;
ICalProperty *prop;
int percent_complete;
status = i_cal_component_get_status (component);
if (status == I_CAL_STATUS_COMPLETED)
return 100;
prop = i_cal_component_get_first_property (component, I_CAL_COMPLETED_PROPERTY);
if (prop)
{
g_object_unref (prop);
return 100;
}
prop = i_cal_component_get_first_property (component, I_CAL_PERCENTCOMPLETE_PROPERTY);
if (!prop)
return 0;
percent_complete = i_cal_property_get_percentcomplete (prop);
g_object_unref (prop);
return CLAMP (percent_complete, 0, 100);
}
static inline time_t
get_component_completed_time (ICalComponent *component,
ICalTimezone *default_zone)
{
return get_time_from_property (component,
I_CAL_COMPLETED_PROPERTY,
i_cal_property_get_completed,
default_zone);
}
static int
get_component_priority (ICalComponent *component)
{
ICalProperty *prop;
int priority;
prop = i_cal_component_get_first_property (component, I_CAL_PRIORITY_PROPERTY);
if (!prop)
return -1;
priority = i_cal_property_get_priority (prop);
g_object_unref (prop);
return priority;
}
static char *
get_source_color (ECalClient *esource)
{
ESource *source;
ECalClientSourceType source_type;
ESourceSelectable *extension;
const gchar *extension_name;
g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
source = e_client_get_source (E_CLIENT (esource));
source_type = e_cal_client_get_source_type (esource);
switch (source_type)
{
case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
extension_name = E_SOURCE_EXTENSION_CALENDAR;
break;
case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
extension_name = E_SOURCE_EXTENSION_TASK_LIST;
break;
case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
case E_CAL_CLIENT_SOURCE_TYPE_LAST:
default:
g_return_val_if_reached (NULL);
}
extension = e_source_get_extension (source, extension_name);
return e_source_selectable_dup_color (extension);
}
static gchar *
get_source_backend_name (ECalClient *esource)
{
ESource *source;
ECalClientSourceType source_type;
ESourceBackend *extension;
const gchar *extension_name;
g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
source = e_client_get_source (E_CLIENT (esource));
source_type = e_cal_client_get_source_type (esource);
switch (source_type)
{
case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
extension_name = E_SOURCE_EXTENSION_CALENDAR;
break;
case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
extension_name = E_SOURCE_EXTENSION_TASK_LIST;
break;
case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
case E_CAL_CLIENT_SOURCE_TYPE_LAST:
default:
g_return_val_if_reached (NULL);
}
extension = e_source_get_extension (source, extension_name);
return e_source_backend_dup_backend_name (extension);
}
static inline gboolean
calendar_appointment_equal (CalendarAppointment *a,
CalendarAppointment *b)
{
GSList *la, *lb;
if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences))
return FALSE;
for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next)
{
CalendarOccurrence *oa = la->data;
CalendarOccurrence *ob = lb->data;
if (oa->start_time != ob->start_time ||
oa->end_time != ob->end_time)
return FALSE;
}
return
g_strcmp0 (a->uid, b->uid) == 0 &&
g_strcmp0 (a->backend_name, b->backend_name) == 0 &&
g_strcmp0 (a->summary, b->summary) == 0 &&
g_strcmp0 (a->description, b->description) == 0 &&
g_strcmp0 (a->color_string, b->color_string) == 0 &&
a->start_time == b->start_time &&
a->end_time == b->end_time &&
a->is_all_day == b->is_all_day;
}
static void
calendar_appointment_copy (CalendarAppointment *appointment,
CalendarAppointment *appointment_copy)
{
GSList *l;
g_assert (appointment != NULL);
g_assert (appointment_copy != NULL);
appointment_copy->occurrences = g_slist_copy (appointment->occurrences);
for (l = appointment_copy->occurrences; l; l = l->next)
{
CalendarOccurrence *occurrence = l->data;
CalendarOccurrence *occurrence_copy;
occurrence_copy = g_new0 (CalendarOccurrence, 1);
occurrence_copy->start_time = occurrence->start_time;
occurrence_copy->end_time = occurrence->end_time;
l->data = occurrence_copy;
}
appointment_copy->uid = g_strdup (appointment->uid);
appointment_copy->backend_name = g_strdup (appointment->backend_name);
appointment_copy->summary = g_strdup (appointment->summary);
appointment_copy->description = g_strdup (appointment->description);
appointment_copy->color_string = g_strdup (appointment->color_string);
appointment_copy->start_time = appointment->start_time;
appointment_copy->end_time = appointment->end_time;
appointment_copy->is_all_day = appointment->is_all_day;
}
static void
calendar_appointment_finalize (CalendarAppointment *appointment)
{
GSList *l;
for (l = appointment->occurrences; l; l = l->next)
g_free (l->data);
g_slist_free (appointment->occurrences);
appointment->occurrences = NULL;
g_free (appointment->uid);
appointment->uid = NULL;
g_free (appointment->rid);
appointment->rid = NULL;
g_free (appointment->backend_name);
appointment->backend_name = NULL;
g_free (appointment->summary);
appointment->summary = NULL;
g_free (appointment->description);
appointment->description = NULL;
g_free (appointment->color_string);
appointment->color_string = NULL;
appointment->start_time = 0;
appointment->is_all_day = FALSE;
}
static void
calendar_appointment_init (CalendarAppointment *appointment,
ICalComponent *component,
CalendarClientSource *source,
ICalTimezone *default_zone)
{
appointment->uid = get_component_uid (component);
appointment->rid = get_component_rid (component);
appointment->backend_name = get_source_backend_name (source->source);
appointment->summary = get_component_summary (component);
appointment->description = get_component_description (component);
appointment->color_string = get_source_color (source->source);
appointment->start_time = get_component_start_time (component, default_zone);
appointment->end_time = get_component_end_time (component, default_zone);
appointment->is_all_day = get_component_is_all_day (component,
appointment->start_time,
default_zone);
}
static ICalTimezone *
resolve_timezone_id (const char *tzid,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
ECalClient *client;
ICalTimezone *retval;
client = E_CAL_CLIENT (user_data);
retval = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
if (!retval)
e_cal_client_get_timezone_sync (client, tzid, &retval, NULL, NULL);
return retval;
}
static gboolean
calendar_appointment_collect_occurrence (ICalComponent *component,
ICalTime *occurrence_start,
ICalTime *occurrence_end,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
time_t start;
time_t end;
CalendarOccurrence *occurrence;
GSList **collect_loc = user_data;
start = i_cal_time_as_timet (occurrence_start);
end = i_cal_time_as_timet (occurrence_end);
occurrence = g_new0 (CalendarOccurrence, 1);
occurrence->start_time = start;
occurrence->end_time = end;
*collect_loc = g_slist_prepend (*collect_loc, occurrence);
return TRUE;
}
static void
calendar_appointment_generate_ocurrences (CalendarAppointment *appointment,
ICalComponent *component,
ECalClient *source,
time_t start,
time_t end,
ICalTimezone *default_zone)
{
ICalTime *start_time;
ICalTime *end_time;
g_assert (appointment->occurrences == NULL);
start_time = i_cal_time_new_from_timet_with_zone (start, FALSE, NULL);
end_time = i_cal_time_new_from_timet_with_zone (end, FALSE, NULL);
e_cal_recur_generate_instances_sync (component,
start_time,
end_time,
calendar_appointment_collect_occurrence,
&appointment->occurrences,
resolve_timezone_id,
source,
default_zone,
NULL,
NULL);
g_object_unref (start_time);
g_object_unref (end_time);
appointment->occurrences = g_slist_reverse (appointment->occurrences);
}
static inline gboolean
calendar_task_equal (CalendarTask *a,
CalendarTask *b)
{
return
g_strcmp0 (a->uid, b->uid) == 0 &&
g_strcmp0 (a->summary, b->summary) == 0 &&
g_strcmp0 (a->description, b->description) == 0 &&
g_strcmp0 (a->color_string, b->color_string) == 0 &&
a->start_time == b->start_time &&
a->due_time == b->due_time &&
a->percent_complete == b->percent_complete &&
a->completed_time == b->completed_time &&
a->priority == b->priority;
}
static void
calendar_task_copy (CalendarTask *task,
CalendarTask *task_copy)
{
g_assert (task != NULL);
g_assert (task_copy != NULL);
task_copy->uid = g_strdup (task->uid);
task_copy->summary = g_strdup (task->summary);
task_copy->description = g_strdup (task->description);
task_copy->color_string = g_strdup (task->color_string);
task_copy->start_time = task->start_time;
task_copy->due_time = task->due_time;
task_copy->percent_complete = task->percent_complete;
task_copy->completed_time = task->completed_time;
task_copy->priority = task->priority;
}
static void
calendar_task_finalize (CalendarTask *task)
{
g_free (task->uid);
task->uid = NULL;
g_free (task->summary);
task->summary = NULL;
g_free (task->description);
task->description = NULL;
g_free (task->color_string);
task->color_string = NULL;
task->percent_complete = 0;
}
static void
calendar_task_init (CalendarTask *task,
ICalComponent *component,
CalendarClientSource *source,
ICalTimezone *default_zone)
{
task->uid = get_component_uid (component);
task->summary = get_component_summary (component);
task->description = get_component_description (component);
task->color_string = get_source_color (source->source);
task->start_time = get_component_start_time (component, default_zone);
task->due_time = get_component_due_time (component, default_zone);
task->percent_complete = get_component_percent_complete (component);
task->completed_time = get_component_completed_time (component, default_zone);
task->priority = get_component_priority (component);
}
void
calendar_event_free (CalendarEvent *event)
{
switch (event->type)
{
case CALENDAR_EVENT_APPOINTMENT:
calendar_appointment_finalize (CALENDAR_APPOINTMENT (event));
break;
case CALENDAR_EVENT_TASK:
calendar_task_finalize (CALENDAR_TASK (event));
break;
case CALENDAR_EVENT_ALL:
default:
g_assert_not_reached ();
break;
}
g_free (event);
}
static CalendarEvent *
calendar_event_new (ICalComponent *component,
CalendarClientSource *source,
ICalTimezone *default_zone)
{
CalendarEvent *event;
ICalComponentKind component_kind;
event = g_new0 (CalendarEvent, 1);
component_kind = i_cal_component_isa (component);
if (component_kind == I_CAL_VEVENT_COMPONENT)
{
event->type = CALENDAR_EVENT_APPOINTMENT;
calendar_appointment_init (CALENDAR_APPOINTMENT (event),
component, source, default_zone);
}
else if (component_kind == I_CAL_VTODO_COMPONENT)
{
event->type = CALENDAR_EVENT_TASK;
calendar_task_init (CALENDAR_TASK (event),
component, source, default_zone);
}
else
{
g_warning ("Unknown calendar component type: %d\n", component_kind);
g_free (event);
return NULL;
}
return event;
}
static CalendarEvent *
calendar_event_copy (CalendarEvent *event)
{
CalendarEvent *retval;
if (!event)
return NULL;
retval = g_new0 (CalendarEvent, 1);
retval->type = event->type;
switch (event->type)
{
case CALENDAR_EVENT_APPOINTMENT:
calendar_appointment_copy (CALENDAR_APPOINTMENT (event),
CALENDAR_APPOINTMENT (retval));
break;
case CALENDAR_EVENT_TASK:
calendar_task_copy (CALENDAR_TASK (event),
CALENDAR_TASK (retval));
break;
case CALENDAR_EVENT_ALL:
default:
g_assert_not_reached ();
break;
}
return retval;
}
static char *
calendar_event_get_uid (CalendarEvent *event)
{
switch (event->type)
{
case CALENDAR_EVENT_APPOINTMENT:
return g_strdup_printf ("%s%s", CALENDAR_APPOINTMENT (event)->uid, CALENDAR_APPOINTMENT (event)->rid ? CALENDAR_APPOINTMENT (event)->rid : "");
break;
case CALENDAR_EVENT_TASK:
return g_strdup (CALENDAR_TASK (event)->uid);
break;
case CALENDAR_EVENT_ALL:
default:
g_assert_not_reached ();
break;
}
return NULL;
}
static gboolean
calendar_event_equal (CalendarEvent *a,
CalendarEvent *b)
{
if (!a && !b)
return TRUE;
if ((a && !b) || (!a && b))
return FALSE;
if (a->type != b->type)
return FALSE;
switch (a->type)
{
case CALENDAR_EVENT_APPOINTMENT:
return calendar_appointment_equal (CALENDAR_APPOINTMENT (a),
CALENDAR_APPOINTMENT (b));
case CALENDAR_EVENT_TASK:
return calendar_task_equal (CALENDAR_TASK (a),
CALENDAR_TASK (b));
case CALENDAR_EVENT_ALL:
default:
break;
}
g_assert_not_reached ();
return FALSE;
}
static void
calendar_event_generate_ocurrences (CalendarEvent *event,
ICalComponent *component,
ECalClient *source,
time_t start,
time_t end,
ICalTimezone *default_zone)
{
if (event->type != CALENDAR_EVENT_APPOINTMENT)
return;
calendar_appointment_generate_ocurrences (CALENDAR_APPOINTMENT (event),
component,
source,
start,
end,
default_zone);
}
static inline void
calendar_event_debug_dump (CalendarEvent *event)
{
#ifdef CALENDAR_ENABLE_DEBUG
switch (event->type)
{
case CALENDAR_EVENT_APPOINTMENT:
{
char *start_str;
char *end_str;
GSList *l;
start_str = CALENDAR_APPOINTMENT (event)->start_time ?
isodate_from_time_t (CALENDAR_APPOINTMENT (event)->start_time) :
g_strdup ("(undefined)");
end_str = CALENDAR_APPOINTMENT (event)->end_time ?
isodate_from_time_t (CALENDAR_APPOINTMENT (event)->end_time) :
g_strdup ("(undefined)");
g_free (start_str);
g_free (end_str);
for (l = CALENDAR_APPOINTMENT (event)->occurrences; l; l = l->next)
{
CalendarOccurrence *occurrence = l->data;
start_str = occurrence->start_time ?
isodate_from_time_t (occurrence->start_time) :
g_strdup ("(undefined)");
end_str = occurrence->end_time ?
isodate_from_time_t (occurrence->end_time) :
g_strdup ("(undefined)");
g_free (start_str);
g_free (end_str);
}
}
break;
case CALENDAR_EVENT_TASK:
{
char *start_str;
char *due_str;
char *completed_str;
start_str = CALENDAR_TASK (event)->start_time ?
isodate_from_time_t (CALENDAR_TASK (event)->start_time) :
g_strdup ("(undefined)");
due_str = CALENDAR_TASK (event)->due_time ?
isodate_from_time_t (CALENDAR_TASK (event)->due_time) :
g_strdup ("(undefined)");
completed_str = CALENDAR_TASK (event)->completed_time ?
isodate_from_time_t (CALENDAR_TASK (event)->completed_time) :
g_strdup ("(undefined)");
g_free (completed_str);
}
break;
default:
g_assert_not_reached ();
break;
}
#endif
}
static inline CalendarClientQuery *
goddamn_this_is_crack (CalendarClientSource *source,
ECalClientView *view,
gboolean *emit_signal)
{
g_assert (view != NULL);
if (source->completed_query.view == view)
{
if (emit_signal)
*emit_signal = TRUE;
return &source->completed_query;
}
else if (source->in_progress_query.view == view)
{
if (emit_signal)
*emit_signal = FALSE;
return &source->in_progress_query;
}
g_assert_not_reached ();
return NULL;
}
static void
calendar_client_handle_query_completed (CalendarClientSource *source,
GError *error,
ECalClientView *view)
{
CalendarClientQuery *query;
query = goddamn_this_is_crack (source, view, NULL);
if (error != NULL)
{
g_warning ("Calendar query failed: %s", error->message);
calendar_client_stop_query (source->client, source, query);
return;
}
g_assert (source->query_in_progress != FALSE);
g_assert (query == &source->in_progress_query);
calendar_client_query_finalize (&source->completed_query);
source->completed_query = source->in_progress_query;
source->query_completed = TRUE;
source->query_in_progress = FALSE;
source->in_progress_query.view = NULL;
source->in_progress_query.events = NULL;
g_signal_emit (source->client, source->changed_signal_id, 0);
}
static void
calendar_client_handle_query_result (CalendarClientSource *source,
GList *objects,
ECalClientView *view)
{
CalendarClientQuery *query;
CalendarClient *client;
gboolean emit_signal;
gboolean events_changed;
GList *l;
time_t month_begin;
time_t month_end;
client = source->client;
query = goddamn_this_is_crack (source, view, &emit_signal);
month_begin = make_time_for_day_begin (1,
client->priv->month,
client->priv->year);
month_end = make_time_for_day_begin (1,
client->priv->month + 1,
client->priv->year);
events_changed = FALSE;
for (l = objects; l; l = l->next)
{
CalendarEvent *event;
CalendarEvent *old_event;
ICalComponent *component = l->data;
char *uid;
event = calendar_event_new (component, source, client->priv->zone);
if (!event)
continue;
calendar_event_generate_ocurrences (event,
component,
source->source,
month_begin,
month_end,
client->priv->zone);
uid = calendar_event_get_uid (event);
old_event = g_hash_table_lookup (query->events, uid);
if (!calendar_event_equal (event, old_event))
{
calendar_event_debug_dump (event);
g_hash_table_replace (query->events, uid, event);
events_changed = TRUE;
}
else
{
calendar_event_free (event);
g_free (uid);
}
}
if (emit_signal && events_changed)
{
g_signal_emit (source->client, source->changed_signal_id, 0);
}
}
static gboolean
check_object_remove (gpointer key,
gpointer value,
gpointer data)
{
char *uid;
size_t len;
uid = data;
len = strlen (uid);
if (len <= strlen (key) && strncmp (uid, key, len) == 0)
{
calendar_event_debug_dump (value);
return TRUE;
}
return FALSE;
}
static void
calendar_client_handle_objects_removed (CalendarClientSource *source,
GList *ids,
ECalClientView *view)
{
CalendarClientQuery *query;
gboolean emit_signal;
gboolean events_changed;
GList *l;
query = goddamn_this_is_crack (source, view, &emit_signal);
events_changed = FALSE;
for (l = ids; l; l = l->next)
{
CalendarEvent *event;
ECalComponentId *id;
const char *uid;
const char *rid;
char *key;
id = l->data;
uid = e_cal_component_id_get_uid (id);
rid = e_cal_component_id_get_rid (id);
key = g_strdup_printf ("%s%s", uid, rid ? rid : "");
if (!rid || !*rid)
{
guint size = g_hash_table_size (query->events);
g_hash_table_foreach_remove (query->events, check_object_remove, (gpointer) uid);
if (size != g_hash_table_size (query->events))
events_changed = TRUE;
}
else if ((event = g_hash_table_lookup (query->events, key)))
{
calendar_event_debug_dump (event);
g_assert (g_hash_table_remove (query->events, key));
events_changed = TRUE;
}
g_free (key);
}
if (emit_signal && events_changed)
{
g_signal_emit (source->client, source->changed_signal_id, 0);
}
}
static void
calendar_client_query_finalize (CalendarClientQuery *query)
{
if (query->view)
g_object_unref (query->view);
query->view = NULL;
if (query->events)
g_hash_table_destroy (query->events);
query->events = NULL;
}
static void
calendar_client_stop_query (CalendarClient *client,
CalendarClientSource *source,
CalendarClientQuery *query)
{
if (query == &source->in_progress_query)
{
g_assert (source->query_in_progress != FALSE);
source->query_in_progress = FALSE;
}
else if (query == &source->completed_query)
{
g_assert (source->query_completed != FALSE);
source->query_completed = FALSE;
}
else
g_assert_not_reached ();
calendar_client_query_finalize (query);
}
static void
calendar_client_start_query (CalendarClient *client,
CalendarClientSource *source,
const char *query)
{
ECalClientView *view = NULL;
GError *error = NULL;
if (!e_cal_client_get_view_sync (source->source, query, &view, NULL, &error))
{
g_warning ("Error preparing the query: '%s': %s",
query, error->message);
g_error_free (error);
return;
}
g_assert (view != NULL);
if (source->query_in_progress)
calendar_client_stop_query (client, source, &source->in_progress_query);
source->query_in_progress = TRUE;
source->in_progress_query.view = view;
source->in_progress_query.events = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) calendar_event_free);
g_signal_connect_swapped (view, "objects-added",
G_CALLBACK (calendar_client_handle_query_result),
source);
g_signal_connect_swapped (view, "objects-modified",
G_CALLBACK (calendar_client_handle_query_result),
source);
g_signal_connect_swapped (view, "objects-removed",
G_CALLBACK (calendar_client_handle_objects_removed),
source);
g_signal_connect_swapped (view, "complete",
G_CALLBACK (calendar_client_handle_query_completed),
source);
e_cal_client_view_start (view, NULL);
}
static void
calendar_client_update_appointments (CalendarClient *client)
{
GSList *l;
char *query;
char *month_begin;
char *month_end;
if (client->priv->month == G_MAXUINT || client->priv->year == G_MAXUINT)
return;
month_begin = make_isodate_for_day_begin (1,
client->priv->month,
client->priv->year);
month_end = make_isodate_for_day_begin (1,
client->priv->month + 1,
client->priv->year);
query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
"(make-time \"%s\")",
month_begin, month_end);
for (l = client->priv->appointment_sources; l; l = l->next)
{
CalendarClientSource *cs = l->data;
calendar_client_start_query (client, cs, query);
}
g_free (month_begin);
g_free (month_end);
g_free (query);
}
/* FIXME:
* perhaps we should use evo's "hide_completed_tasks" pref?
*/
static void
calendar_client_update_tasks (CalendarClient *client)
{
GSList *l;
char *query;
#ifdef FIX_BROKEN_TASKS_QUERY
/* FIXME: this doesn't work for tasks without a start or
* due date
* Look at filter_task() to see the behaviour we
* want.
*/
char *day_begin;
char *day_end;
if (client->priv->day == G_MAXUINT ||
client->priv->month == G_MAXUINT ||
client->priv->year == G_MAXUINT)
return;
day_begin = make_isodate_for_day_begin (client->priv->day,
client->priv->month,
client->priv->year);
day_end = make_isodate_for_day_begin (client->priv->day + 1,
client->priv->month,
client->priv->year);
if (!day_begin || !day_end)
{
g_warning ("Cannot run query with invalid date: %dd %dy %dm\n",
client->priv->day,
client->priv->month,
client->priv->year);
g_free (day_begin);
g_free (day_end);
return;
}
query = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") "
"(make-time \"%s\")) "
"(or (not is-completed?) "
"(and (is-completed?) "
"(not (completed-before? (make-time \"%s\"))))))",
day_begin, day_end, day_begin);
#else
query = g_strdup ("#t");
#endif /* FIX_BROKEN_TASKS_QUERY */
for (l = client->priv->task_sources; l; l = l->next)
{
CalendarClientSource *cs = l->data;
calendar_client_start_query (client, cs, query);
}
#ifdef FIX_BROKEN_TASKS_QUERY
g_free (day_begin);
g_free (day_end);
#endif
g_free (query);
}
static void
calendar_client_source_finalize (CalendarClientSource *source)
{
source->client = NULL;
if (source->source) {
g_object_unref (source->source);
}
source->source = NULL;
calendar_client_query_finalize (&source->completed_query);
calendar_client_query_finalize (&source->in_progress_query);
source->query_completed = FALSE;
source->query_in_progress = FALSE;
}
static int
compare_calendar_sources (CalendarClientSource *s1,
CalendarClientSource *s2)
{
return (s1->source == s2->source) ? 0 : 1;
}
static GSList *
calendar_client_update_sources_list (CalendarClient *client,
GSList *sources,
GList *esources,
guint changed_signal_id)
{
GList *link;
GSList *retval, *l;
retval = NULL;
for (link = esources; link != NULL; link = g_list_next (link->next))
{
CalendarClientSource dummy_source;
CalendarClientSource *new_source;
GSList *s;
ECalClient *esource = link->data;
dummy_source.source = esource;
if ((s = g_slist_find_custom (sources,
&dummy_source,
(GCompareFunc) compare_calendar_sources)))
{
new_source = s->data;
sources = g_slist_delete_link (sources, s);
}
else
{
new_source = g_new0 (CalendarClientSource, 1);
new_source->client = client;
new_source->source = g_object_ref (esource);
new_source->changed_signal_id = changed_signal_id;
}
retval = g_slist_prepend (retval, new_source);
}
for (l = sources; l; l = l->next)
{
CalendarClientSource *source = l->data;
calendar_client_source_finalize (source);
g_free (source);
}
g_slist_free (sources);
return retval;
}
static void
calendar_client_appointment_sources_changed (CalendarClient *client)
{
GList *list;
list = calendar_sources_get_appointment_clients (client->priv->calendar_sources);
client->priv->appointment_sources = calendar_client_update_sources_list (client,
client->priv->appointment_sources,
list,
signals [APPOINTMENTS_CHANGED]);
load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
calendar_client_update_appointments (client);
g_list_free (list);
}
static void
calendar_client_task_sources_changed (CalendarClient *client)
{
GList *list;
list = calendar_sources_get_task_clients (client->priv->calendar_sources);
client->priv->task_sources = calendar_client_update_sources_list (client,
client->priv->task_sources,
list,
signals [TASKS_CHANGED]);
load_calendars (client, CALENDAR_EVENT_TASK);
calendar_client_update_tasks (client);
g_list_free (list);
}
void
calendar_client_get_date (CalendarClient *client,
guint *year,
guint *month,
guint *day)
{
g_return_if_fail (CALENDAR_IS_CLIENT (client));
if (year)
*year = client->priv->year;
if (month)
*month = client->priv->month;
if (day)
*day = client->priv->day;
}
void
calendar_client_select_month (CalendarClient *client,
guint month,
guint year)
{
g_return_if_fail (CALENDAR_IS_CLIENT (client));
g_return_if_fail (month <= 11);
if (client->priv->year != year || client->priv->month != month)
{
client->priv->month = month;
client->priv->year = year;
calendar_client_update_appointments (client);
calendar_client_update_tasks (client);
g_object_freeze_notify (G_OBJECT (client));
g_object_notify (G_OBJECT (client), "month");
g_object_notify (G_OBJECT (client), "year");
g_object_thaw_notify (G_OBJECT (client));
}
}
void
calendar_client_select_day (CalendarClient *client,
guint day)
{
g_return_if_fail (CALENDAR_IS_CLIENT (client));
g_return_if_fail (day <= 31);
if (client->priv->day != day)
{
client->priv->day = day;
/* don't need to update appointments unless
* the selected month changes
*/
#ifdef FIX_BROKEN_TASKS_QUERY
calendar_client_update_tasks (client);
#endif
g_object_notify (G_OBJECT (client), "day");
}
}
typedef struct
{
CalendarClient *client;
GSList *events;
time_t start_time;
time_t end_time;
} FilterData;
typedef void (* CalendarEventFilterFunc) (const char *uid,
CalendarEvent *event,
FilterData *filter_data);
static void
filter_appointment (const char *uid,
CalendarEvent *event,
FilterData *filter_data)
{
GSList *occurrences, *l;
if (event->type != CALENDAR_EVENT_APPOINTMENT)
return;
occurrences = CALENDAR_APPOINTMENT (event)->occurrences;
CALENDAR_APPOINTMENT (event)->occurrences = NULL;
for (l = occurrences; l; l = l->next)
{
CalendarOccurrence *occurrence = l->data;
time_t start_time = occurrence->start_time;
time_t end_time = occurrence->end_time;
if ((start_time >= filter_data->start_time &&
start_time < filter_data->end_time) ||
(start_time <= filter_data->start_time &&
(end_time - 1) > filter_data->start_time))
{
CalendarEvent *new_event;
new_event = calendar_event_copy (event);
CALENDAR_APPOINTMENT (new_event)->start_time = occurrence->start_time;
CALENDAR_APPOINTMENT (new_event)->end_time = occurrence->end_time;
filter_data->events = g_slist_prepend (filter_data->events, new_event);
}
}
CALENDAR_APPOINTMENT (event)->occurrences = occurrences;
}
static void
filter_task (const char *uid,
CalendarEvent *event,
FilterData *filter_data)
{
#ifdef FIX_BROKEN_TASKS_QUERY
CalendarTask *task;
#endif
if (event->type != CALENDAR_EVENT_TASK)
return;
#ifdef FIX_BROKEN_TASKS_QUERY
task = CALENDAR_TASK (event);
if (task->start_time && task->start_time > filter_data->start_time)
return;
if (task->completed_time &&
(task->completed_time < filter_data->start_time ||
task->completed_time > filter_data->end_time))
return;
#endif /* FIX_BROKEN_TASKS_QUERY */
filter_data->events = g_slist_prepend (filter_data->events,
calendar_event_copy (event));
}
static GSList *
calendar_client_filter_events (CalendarClient *client,
GSList *sources,
CalendarEventFilterFunc filter_func,
time_t start_time,
time_t end_time)
{
FilterData filter_data;
GSList *l;
GSList *retval;
if (!sources)
return NULL;
filter_data.client = client;
filter_data.events = NULL;
filter_data.start_time = start_time;
filter_data.end_time = end_time;
retval = NULL;
for (l = sources; l; l = l->next)
{
CalendarClientSource *source = l->data;
if (source->query_completed)
{
filter_data.events = NULL;
g_hash_table_foreach (source->completed_query.events,
(GHFunc) filter_func,
&filter_data);
filter_data.events = g_slist_reverse (filter_data.events);
retval = g_slist_concat (retval, filter_data.events);
}
}
return retval;
}
GSList *
calendar_client_get_events (CalendarClient *client,
CalendarEventType event_mask)
{
GSList *appointments;
GSList *tasks;
time_t day_begin;
time_t day_end;
g_return_val_if_fail (CALENDAR_IS_CLIENT (client), NULL);
g_return_val_if_fail (client->priv->day != G_MAXUINT, NULL);
g_return_val_if_fail (client->priv->month != G_MAXUINT, NULL);
g_return_val_if_fail (client->priv->year != G_MAXUINT, NULL);
day_begin = make_time_for_day_begin (client->priv->day,
client->priv->month,
client->priv->year);
day_end = make_time_for_day_begin (client->priv->day + 1,
client->priv->month,
client->priv->year);
appointments = NULL;
if (event_mask & CALENDAR_EVENT_APPOINTMENT)
{
appointments = calendar_client_filter_events (client,
client->priv->appointment_sources,
filter_appointment,
day_begin,
day_end);
}
tasks = NULL;
if (event_mask & CALENDAR_EVENT_TASK)
{
tasks = calendar_client_filter_events (client,
client->priv->task_sources,
filter_task,
day_begin,
day_end);
}
return g_slist_concat (appointments, tasks);
}
static inline int
day_from_time_t (time_t t)
{
struct tm *tm = localtime (&t);
g_assert (tm == NULL || (tm->tm_mday >=1 && tm->tm_mday <= 31));
return tm ? tm->tm_mday : 0;
}
void
calendar_client_foreach_appointment_day (CalendarClient *client,
CalendarDayIter iter_func,
gpointer user_data)
{
GSList *appointments, *l;
gboolean marked_days [32] = { FALSE, };
time_t month_begin;
time_t month_end;
int i;
g_return_if_fail (CALENDAR_IS_CLIENT (client));
g_return_if_fail (iter_func != NULL);
g_return_if_fail (client->priv->month != G_MAXUINT);
g_return_if_fail (client->priv->year != G_MAXUINT);
month_begin = make_time_for_day_begin (1,
client->priv->month,
client->priv->year);
month_end = make_time_for_day_begin (1,
client->priv->month + 1,
client->priv->year);
appointments = calendar_client_filter_events (client,
client->priv->appointment_sources,
filter_appointment,
month_begin,
month_end);
for (l = appointments; l; l = l->next)
{
CalendarAppointment *appointment = l->data;
if (appointment->start_time)
{
time_t day_time = appointment->start_time;
if (day_time >= month_begin)
marked_days [day_from_time_t (day_time)] = TRUE;
if (appointment->end_time)
{
int day_offset;
int duration = appointment->end_time - appointment->start_time;
/* mark the days for the appointment, no need to add an extra one when duration is a multiple of 86400 */
for (day_offset = 1; day_offset <= duration / 86400 && duration != day_offset * 86400; day_offset++)
{
time_t day_tm = appointment->start_time + day_offset * 86400;
if (day_tm > month_end)
break;
if (day_tm >= month_begin)
marked_days [day_from_time_t (day_tm)] = TRUE;
}
}
}
calendar_event_free (CALENDAR_EVENT (appointment));
}
g_slist_free (appointments);
for (i = 1; i < 32; i++)
{
if (marked_days [i])
iter_func (client, i, user_data);
}
}
void
calendar_client_set_task_completed (CalendarClient *client,
char *task_uid,
gboolean task_completed,
guint percent_complete)
{
GSList *l;
ECalClient *esource;
ICalComponent *component;
ICalProperty *prop;
ICalPropertyStatus status;
g_return_if_fail (CALENDAR_IS_CLIENT (client));
g_return_if_fail (task_uid != NULL);
g_return_if_fail (task_completed == FALSE || percent_complete == 100);
component = NULL;
esource = NULL;
for (l = client->priv->task_sources; l; l = l->next)
{
CalendarClientSource *source = l->data;
esource = source->source;
e_cal_client_get_object_sync (esource, task_uid, NULL, &component, NULL, NULL);
if (component)
break;
}
if (!component)
{
g_warning ("Cannot locate task with uid = '%s'\n", task_uid);
return;
}
g_assert (esource != NULL);
/* Completed time */
prop = i_cal_component_get_first_property (component, I_CAL_COMPLETED_PROPERTY);
if (task_completed)
{
ICalTime *completed_time;
completed_time = i_cal_time_new_current_with_zone (client->priv->zone);
if (!prop)
{
i_cal_component_take_property (component,
i_cal_property_new_completed (completed_time));
}
else
{
i_cal_property_set_completed (prop, completed_time);
}
}
else if (prop)
{
i_cal_component_remove_property (component, prop);
}
g_clear_object (&prop);
/* Percent complete */
prop = i_cal_component_get_first_property (component, I_CAL_PERCENTCOMPLETE_PROPERTY);
if (!prop)
{
i_cal_component_take_property (component,
i_cal_property_new_percentcomplete (percent_complete));
}
else
{
i_cal_property_set_percentcomplete (prop, percent_complete);
}
g_clear_object (&prop);
/* Status */
status = task_completed ? I_CAL_STATUS_COMPLETED : I_CAL_STATUS_NEEDSACTION;
prop = i_cal_component_get_first_property (component, I_CAL_STATUS_PROPERTY);
if (prop)
{
i_cal_property_set_status (prop, status);
}
else
{
i_cal_component_take_property (component, i_cal_property_new_status (status));
}
g_clear_object (&prop);
e_cal_client_modify_object_sync (esource,
component,
E_CAL_OBJ_MOD_ALL,
0,
NULL,
NULL);
}
gboolean
calendar_client_create_task (CalendarClient *client,
const char *summary)
{
GSList *l;
ECalClient *task_client = NULL;
ICalComponent *vtodo_component;
gchar *uid;
GError *error = NULL;
gboolean success = FALSE;
g_return_val_if_fail (CALENDAR_IS_CLIENT (client), FALSE);
g_return_val_if_fail (summary != NULL && *summary != '\0', FALSE);
/* Use the first available task source (like the existing code does) */
for (l = client->priv->task_sources; l; l = l->next)
{
CalendarClientSource *source = l->data;
task_client = source->source;
if (task_client)
break;
}
if (!task_client)
{
g_warning ("No task client available for task creation");
return FALSE;
}
/* Create a simple VTODO component */
vtodo_component = i_cal_component_new (I_CAL_VTODO_COMPONENT);
/* Generate UID */
uid = e_util_generate_uid ();
i_cal_component_set_uid (vtodo_component, uid);
g_free (uid);
/* Set summary */
i_cal_component_set_summary (vtodo_component, summary);
/* Set created time */
ICalTime *now = i_cal_time_new_current_with_zone (client->priv->zone);
i_cal_component_set_dtstamp (vtodo_component, now);
g_object_unref (now);
/* Create the task */
success = e_cal_client_create_object_sync (task_client,
vtodo_component,
E_CAL_OPERATION_FLAG_NONE,
NULL, /* out uid */
NULL, /* cancellable */
&error);
if (error)
{
g_warning ("Failed to create task: %s", error->message);
g_error_free (error);
success = FALSE;
}
/* Cleanup */
g_object_unref (vtodo_component);
return success;
}
#endif /* HAVE_EDS */