/* Evolution calendar utilities and types
*
* Copyright (C) 2000 Ximian, Inc.
* Copyright (C) 2000 Ximian, Inc.
*
* Author: Federico Mena-Quintero <federico@ximian.com>
*
* 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.
*/
#include <config.h>
#include <stdlib.h>
#include <glib.h>
#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-i18n.h>
#include "cal-util.h"
/**
* cal_obj_instance_list_free:
* @list: List of #CalObjInstance structures.
*
* Frees a list of #CalObjInstance structures.
**/
void
cal_obj_instance_list_free (GList *list)
{
CalObjInstance *i;
GList *l;
for (l = list; l; l = l->next) {
i = l->data;
g_assert (i != NULL);
g_assert (i->uid != NULL);
g_free (i->uid);
g_free (i);
}
g_list_free (list);
}
/**
* cal_obj_uid_list_free:
* @list: List of strings with unique identifiers.
*
* Frees a list of unique identifiers for calendar objects.
**/
void
cal_obj_uid_list_free (GList *list)
{
GList *l;
for (l = list; l; l = l->next) {
char *uid;
uid = l->data;
g_assert (uid != NULL);
g_free (uid);
}
g_list_free (list);
}
icalcomponent *
cal_util_new_top_level (void)
{
icalcomponent *icalcomp;
icalproperty *prop;
icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
/* RFC 2445, section 4.7.1 */
prop = icalproperty_new_calscale ("GREGORIAN");
icalcomponent_add_property (icalcomp, prop);
/* RFC 2445, section 4.7.3 */
prop = icalproperty_new_prodid ("-//Ximian//NONSGML Evolution Calendar//EN");
icalcomponent_add_property (icalcomp, prop);
/* RFC 2445, section 4.7.4. This is the iCalendar spec version, *NOT*
* the product version! Do not change this!
*/
prop = icalproperty_new_version ("2.0");
icalcomponent_add_property (icalcomp, prop);
return icalcomp;
}
/* Computes the range of time in which recurrences should be generated for a
* component in order to compute alarm trigger times.
*/
static void
compute_alarm_range (CalComponent *comp, GList *alarm_uids, time_t start, time_t end,
time_t *alarm_start, time_t *alarm_end)
{
GList *l;
time_t repeat_time;
*alarm_start = start;
*alarm_end = end;
repeat_time = 0;
for (l = alarm_uids; l; l = l->next) {
const char *auid;
CalComponentAlarm *alarm;
CalAlarmTrigger trigger;
struct icaldurationtype *dur;
time_t dur_time;
CalAlarmRepeat repeat;
auid = l->data;
alarm = cal_component_get_alarm (comp, auid);
g_assert (alarm != NULL);
cal_component_alarm_get_trigger (alarm, &trigger);
cal_component_alarm_get_repeat (alarm, &repeat);
cal_component_alarm_free (alarm);
switch (trigger.type) {
case CAL_ALARM_TRIGGER_NONE:
case CAL_ALARM_TRIGGER_ABSOLUTE:
break;
case CAL_ALARM_TRIGGER_RELATIVE_START:
case CAL_ALARM_TRIGGER_RELATIVE_END:
dur = &trigger.u.rel_duration;
dur_time = icaldurationtype_as_int (*dur);
repeat_time = MAX (repeat_time,
(repeat.repetitions
* icaldurationtype_as_int (repeat.duration)));
if (dur->is_neg)
/* If the duration is negative then dur_time
* will be negative as well; that is why we
* subtract to expand the range.
*/
*alarm_end = MAX (*alarm_end, end - dur_time);
else
*alarm_start = MIN (*alarm_start, start - dur_time);
break;
default:
g_assert_not_reached ();
}
}
alarm_start -= repeat_time;
g_assert (*alarm_start <= *alarm_end);
}
/* Closure data to generate alarm occurrences */
struct alarm_occurrence_data {
/* These are the info we have */
GList *alarm_uids;
time_t start;
time_t end;
/* This is what we compute */
GSList *triggers;
int n_triggers;
};
static void
add_trigger (struct alarm_occurrence_data *aod, const char *auid, time_t trigger,
time_t occur_start, time_t occur_end)
{
CalAlarmInstance *instance;
instance = g_new (CalAlarmInstance, 1);
instance->auid = auid;
instance->trigger = trigger;
instance->occur_start = occur_start;
instance->occur_end = occur_end;
aod->triggers = g_slist_prepend (aod->triggers, instance);
aod->n_triggers++;
}
/* Callback used from cal_recur_generate_instances(); generates triggers for all
* of a component's RELATIVE alarms.
*/
static gboolean
add_alarm_occurrences_cb (CalComponent *comp, time_t start, time_t end, gpointer data)
{
struct alarm_occurrence_data *aod;
GList *l;
aod = data;
for (l = aod->alarm_uids; l; l = l->next) {
const char *auid;
CalComponentAlarm *alarm;
CalAlarmTrigger trigger;
CalAlarmRepeat repeat;
struct icaldurationtype *dur;
time_t dur_time;
time_t occur_time, trigger_time;
auid = l->data;
alarm = cal_component_get_alarm (comp, auid);
g_assert (alarm != NULL);
cal_component_alarm_get_trigger (alarm, &trigger);
cal_component_alarm_get_repeat (alarm, &repeat);
cal_component_alarm_free (alarm);
if (trigger.type != CAL_ALARM_TRIGGER_RELATIVE_START
&& trigger.type != CAL_ALARM_TRIGGER_RELATIVE_END)
continue;
dur = &trigger.u.rel_duration;
dur_time = icaldurationtype_as_int (*dur);
if (trigger.type == CAL_ALARM_TRIGGER_RELATIVE_START)
occur_time = start;
else
occur_time = end;
/* If dur->is_neg is true then dur_time will already be
* negative. So we do not need to test for dur->is_neg here; we
* can simply add the dur_time value to the occur_time and get
* the correct result.
*/
trigger_time = occur_time + dur_time;
/* Add repeating alarms */
if (repeat.repetitions != 0) {
int i;
time_t repeat_time;
repeat_time = icaldurationtype_as_int (repeat.duration);
for (i = 0; i < repeat.repetitions; i++) {
time_t t;
t = trigger_time + (i + 1) * repeat_time;
if (t >= aod->start && t < aod->end)
add_trigger (aod, auid, t, start, end);
}
}
/* Add the trigger itself */
if (trigger_time >= aod->start && trigger_time < aod->end)
add_trigger (aod, auid, trigger_time, start, end);
}
return TRUE;
}
/* Generates the absolute triggers for a component */
static void
generate_absolute_triggers (CalComponent *comp, struct alarm_occurrence_data *aod,
CalRecurResolveTimezoneFn resolve_tzid,
gpointer user_data,
icaltimezone *default_timezone)
{
GList *l;
CalComponentDateTime dt_start, dt_end;
cal_component_get_dtstart (comp, &dt_start);
cal_component_get_dtend (comp, &dt_end);
for (l = aod->alarm_uids; l; l = l->next) {
const char *auid;
CalComponentAlarm *alarm;
CalAlarmRepeat repeat;
CalAlarmTrigger trigger;
time_t abs_time;
time_t occur_start, occur_end;
icaltimezone *zone;
auid = l->data;
alarm = cal_component_get_alarm (comp, auid);
g_assert (alarm != NULL);
cal_component_alarm_get_trigger (alarm, &trigger);
cal_component_alarm_get_repeat (alarm, &repeat);
cal_component_alarm_free (alarm);
if (trigger.type != CAL_ALARM_TRIGGER_ABSOLUTE)
continue;
/* Absolute triggers are always in UTC; see RFC 2445 section 4.8.6.3 */
zone = icaltimezone_get_utc_timezone ();
abs_time = icaltime_as_timet_with_zone (trigger.u.abs_time, zone);
/* No particular occurrence, so just use the times from the component */
if (dt_start.value) {
if (dt_start.tzid && !dt_start.value->is_date)
zone = (* resolve_tzid) (dt_start.tzid, user_data);
else
zone = default_timezone;
occur_start = icaltime_as_timet_with_zone (*dt_start.value, zone);
} else
occur_start = -1;
if (dt_end.value) {
if (dt_end.tzid && !dt_end.value->is_date)
zone = (* resolve_tzid) (dt_end.tzid, user_data);
else
zone = default_timezone;
occur_end = icaltime_as_timet_with_zone (*dt_end.value, zone);
} else
occur_end = -1;
/* Add repeating alarms */
if (repeat.repetitions != 0) {
int i;
time_t repeat_time;
repeat_time = icaldurationtype_as_int (repeat.duration);
for (i = 0; i < repeat.repetitions; i++) {
time_t t;
t = abs_time + (i + 1) * repeat_time;
if (t >= aod->start && t < aod->end)
add_trigger (aod, auid, t, occur_start, occur_end);
}
}
/* Add the trigger itself */
if (abs_time >= aod->start && abs_time < aod->end)
add_trigger (aod, auid, abs_time, occur_start, occur_end);
}
cal_component_free_datetime (&dt_start);
cal_component_free_datetime (&dt_end);
}
/* Compares two alarm instances; called from g_slist_sort() */
static gint
compare_alarm_instance (gconstpointer a, gconstpointer b)
{
const CalAlarmInstance *aia, *aib;
aia = a;
aib = b;
if (aia->trigger < aib->trigger)
return -1;
else if (aia->trigger > aib->trigger)
return 1;
else
return 0;
}
/**
* cal_util_generate_alarms_for_comp
* @comp: the CalComponent to generate alarms from
* @start: start time
* @end: end time
* @resolve_tzid: callback for resolving timezones
* @user_data: data to be passed to the resolve_tzid callback
* @default_timezone: the timezone used to resolve DATE and floating DATE-TIME
* values.
*
* Generates alarm instances for a calendar component. Returns the instances
* structure, or NULL if no alarm instances occurred in the specified time
* range.
*/
CalComponentAlarms *
cal_util_generate_alarms_for_comp (CalComponent *comp,
time_t start,
time_t end,
CalRecurResolveTimezoneFn resolve_tzid,
gpointer user_data,
icaltimezone *default_timezone)
{
GList *alarm_uids;
time_t alarm_start, alarm_end;
struct alarm_occurrence_data aod;
CalComponentAlarms *alarms;
if (!cal_component_has_alarms (comp))
return NULL;
alarm_uids = cal_component_get_alarm_uids (comp);
compute_alarm_range (comp, alarm_uids, start, end, &alarm_start, &alarm_end);
aod.alarm_uids = alarm_uids;
aod.start = start;
aod.end = end;
aod.triggers = NULL;
aod.n_triggers = 0;
cal_recur_generate_instances (comp, alarm_start, alarm_end,
add_alarm_occurrences_cb, &aod,
resolve_tzid, user_data,
default_timezone);
/* We add the ABSOLUTE triggers separately */
generate_absolute_triggers (comp, &aod, resolve_tzid, user_data, default_timezone);
if (aod.n_triggers == 0)
return NULL;
/* Create the component alarm instances structure */
alarms = g_new (CalComponentAlarms, 1);
alarms->comp = comp;
gtk_object_ref (GTK_OBJECT (alarms->comp));
alarms->alarms = g_slist_sort (aod.triggers, compare_alarm_instance);
return alarms;
}
/**
* cal_util_generate_alarms_for_list
* @comps: list of CalComponent's
* @start: start time
* @end: end time
* @comp_alarms: list to be returned
* @resolve_tzid: callback for resolving timezones
* @user_data: data to be passed to the resolve_tzid callback
* @default_timezone: the timezone used to resolve DATE and floating DATE-TIME
* values.
*
* Iterates through all the components in the comps list and generates alarm
* instances for them; putting them in the comp_alarms list.
*
* Returns: the number of elements it added to that list.
*/
int
cal_util_generate_alarms_for_list (GList *comps,
time_t start,
time_t end,
GSList **comp_alarms,
CalRecurResolveTimezoneFn resolve_tzid,
gpointer user_data,
icaltimezone *default_timezone)
{
GList *l;
int n;
n = 0;
for (l = comps; l; l = l->next) {
CalComponent *comp;
CalComponentAlarms *alarms;
comp = CAL_COMPONENT (l->data);
alarms = cal_util_generate_alarms_for_comp (comp, start, end, resolve_tzid, user_data, default_timezone);
if (alarms) {
*comp_alarms = g_slist_prepend (*comp_alarms, alarms);
n++;
}
}
return n;
}
/* Converts an iCalendar PRIORITY value to a translated string. Any unknown
priority value (i.e. not 0-9) will be returned as "" (undefined). */
char *
cal_util_priority_to_string (int priority)
{
char *retval;
if (priority <= 0)
retval = "";
else if (priority <= 4)
retval = _("High");
else if (priority == 5)
retval = _("Normal");
else if (priority <= 9)
retval = _("Low");
else
retval = "";
return retval;
}
/* Converts a translated priority string to an iCalendar priority value.
Returns -1 if the priority string is not valid. */
int
cal_util_priority_from_string (const char *string)
{
int priority;
/* An empty string is the same as 'None'. */
if (!string || !string[0] || !g_strcasecmp (string, _("Undefined")))
priority = 0;
else if (!g_strcasecmp (string, _("High")))
priority = 3;
else if (!g_strcasecmp (string, _("Normal")))
priority = 5;
else if (!g_strcasecmp (string, _("Low")))
priority = 7;
else
priority = -1;
return priority;
}