/* 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 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <stdlib.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;
*alarm_start = start;
*alarm_end = end;
for (l = alarm_uids; l; l = l->next) {
const char *auid;
CalComponentAlarm *alarm;
CalAlarmTrigger trigger;
struct icaldurationtype *dur;
time_t dur_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_free (alarm);
switch (trigger.type) {
case CAL_ALARM_TRIGGER_NONE:
case CAL_ALARM_TRIGGER_ABSOLUTE:
continue;
case CAL_ALARM_TRIGGER_RELATIVE_START:
case CAL_ALARM_TRIGGER_RELATIVE_END:
dur = &trigger.u.rel_duration;
dur_time = icaldurationtype_as_int (*dur);
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 ();
}
}
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;
};
/* 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;
struct icaldurationtype *dur;
time_t dur_time;
time_t occur_time, trigger_time;
CalAlarmInstance *instance;
auid = l->data;
alarm = cal_component_get_alarm (comp, auid);
g_assert (alarm != NULL);
cal_component_alarm_get_trigger (alarm, &trigger);
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;
if (trigger_time < aod->start || trigger_time >= aod->end)
continue;
instance = g_new (CalAlarmInstance, 1);
instance->auid = auid;
instance->trigger = trigger_time;
instance->occur_start = start;
instance->occur_end = end;
aod->triggers = g_slist_prepend (aod->triggers, instance);
aod->n_triggers++;
}
return TRUE;
}
/* Generates the absolute triggers for a component */
static void
generate_absolute_triggers (CalComponent *comp, struct alarm_occurrence_data *aod)
{
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;
CalAlarmTrigger trigger;
time_t abs_time;
CalAlarmInstance *instance;
auid = l->data;
alarm = cal_component_get_alarm (comp, auid);
g_assert (alarm != NULL);
cal_component_alarm_get_trigger (alarm, &trigger);
cal_component_alarm_free (alarm);
if (trigger.type != CAL_ALARM_TRIGGER_ABSOLUTE)
continue;
abs_time = icaltime_as_timet (trigger.u.abs_time);
if (abs_time < aod->start || abs_time >= aod->end)
continue;
instance = g_new (CalAlarmInstance, 1);
instance->auid = auid;
instance->trigger = abs_time;
/* No particular occurrence, so just use the times from the component */
if (dt_start.value)
instance->occur_start = icaltime_as_timet (*dt_start.value);
else
instance->occur_start = -1;
if (dt_end.value)
instance->occur_end = icaltime_as_timet (*dt_end.value);
else
instance->occur_end = -1;
aod->triggers = g_slist_prepend (aod->triggers, instance);
aod->n_triggers++;
}
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
*
* 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)
{
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);
/* We add the ABSOLUTE triggers separately */
generate_absolute_triggers (comp, &aod);
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
*
* 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)
{
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);
if (alarms) {
*comp_alarms = g_slist_prepend (*comp_alarms, alarms);
n++;
}
}
return n;
}