/*
* Calendar manager object
*
* This keeps track of a given calendar. Eventually this will abtract everything
* related to getting calendars/saving calendars locally or to a remote Calendar Service
*
* Copyright (C) 1998, 1999 the Free Software Foundation
*
* Authors:
* Miguel de Icaza (miguel@gnu.org)
* Federico Mena (quartic@gimp.org)
*
*/
#include <gnome.h>
#include <stdio.h>
#include <config.h>
#include <unistd.h>
#include <sys/stat.h>
#include "calendar.h"
#include "alarm.h"
#include "timeutil.h"
#include "../libversit/vcc.h"
#ifdef HAVE_TZNAME
extern char *tzname[2];
#endif
/* Our day range */
time_t calendar_day_begin, calendar_day_end;
static void calendar_init_alarms (Calendar *cal);
static void calendar_set_day (void);
Calendar *
calendar_new (char *title,CalendarNewOptions options)
{
Calendar *cal;
cal = g_new0 (Calendar, 1);
cal->title = g_strdup (title);
if (options & CALENDAR_USE_ICAL)
cal->format = CAL_ICAL;
else
cal->format = CAL_VCAL;
if ((calendar_day_begin == 0) || (calendar_day_end == 0))
calendar_set_day ();
cal->event_hash = g_hash_table_new (g_str_hash, g_str_equal);
if (options & CALENDAR_INIT_ALARMS) {
calendar_init_alarms (cal);
}
return cal;
}
static void
try_add (iCalObject *ico, CalendarAlarm *alarm, time_t start, time_t end)
{
alarm->trigger = start-alarm->offset;
if (alarm->trigger < calendar_day_begin)
return;
if (alarm->trigger > calendar_day_end)
return;
alarm_add (alarm, &calendar_notify, ico);
}
static int
add_object_alarms (iCalObject *obj, time_t start, time_t end, void *closure)
{
if (obj->aalarm.enabled)
try_add (obj, &obj->aalarm, start, end);
if (obj->dalarm.enabled)
try_add (obj, &obj->dalarm, start, end);
if (obj->palarm.enabled)
try_add (obj,&obj->palarm, start, end);
if (obj->malarm.enabled)
try_add (obj, &obj->malarm, start, end);
return TRUE;
}
#define max(a,b) ((a > b) ? a : b)
static void
ical_object_try_alarms (iCalObject *obj)
{
int ao, po, od, mo;
int max_o;
ao = alarm_compute_offset (&obj->aalarm);
po = alarm_compute_offset (&obj->palarm);
od = alarm_compute_offset (&obj->dalarm);
mo = alarm_compute_offset (&obj->malarm);
max_o = max (ao, max (po, max (od, mo)));
if (max_o == -1)
return;
ical_object_generate_events (obj, calendar_day_begin, calendar_day_end + max_o, add_object_alarms, obj);
}
void
calendar_add_object (Calendar *cal, iCalObject *obj)
{
g_return_if_fail (cal != NULL);
g_return_if_fail (obj != NULL);
g_return_if_fail (obj->uid != NULL);
obj->new = 0;
switch (obj->type){
case ICAL_EVENT:
g_hash_table_insert (cal->event_hash, obj->uid, obj);
cal->events = g_list_prepend (cal->events, obj);
ical_object_try_alarms (obj);
#ifdef DEBUGGING_MAIL_ALARM
obj->malarm.trigger = 0;
calendar_notify (0, obj);
#endif
break;
case ICAL_TODO:
cal->todo = g_list_prepend (cal->todo, obj);
break;
case ICAL_JOURNAL:
cal->journal = g_list_prepend (cal->journal, obj);
break;
default:
g_assert_not_reached ();
}
if (!obj->uid){
char buffer [80];
snprintf (buffer, sizeof (buffer), "GnomeCalendar-%ld\n", time (NULL));
obj->uid = g_strdup (buffer);
}
cal->modified = TRUE;
obj->last_mod = time (NULL);
}
void
calendar_remove_object (Calendar *cal, iCalObject *obj)
{
switch (obj->type){
case ICAL_EVENT:
cal->events = g_list_remove (cal->events, obj);
g_hash_table_remove (cal->event_hash, obj->uid);
break;
case ICAL_TODO:
cal->todo = g_list_remove (cal->todo, obj);
break;
case ICAL_JOURNAL:
cal->journal = g_list_remove (cal->journal, obj);
break;
default:
g_assert_not_reached ();
}
cal->modified = TRUE;
}
void
calendar_destroy (Calendar *cal)
{
g_list_foreach (cal->events, (GFunc) ical_object_destroy, NULL);
g_list_free (cal->events);
g_list_foreach (cal->todo, (GFunc) ical_object_destroy, NULL);
g_list_free (cal->todo);
g_list_foreach (cal->journal, (GFunc) ical_object_destroy, NULL);
g_list_free (cal->journal);
g_hash_table_destroy (cal->event_hash);
if (cal->title)
g_free (cal->title);
if (cal->filename)
g_free (cal->filename);
g_free (cal);
}
void
calendar_iterate_on_objects (GList *objects, time_t start, time_t end, calendarfn cb, void *closure)
{
for (; objects; objects = objects->next){
iCalObject *object = objects->data;
ical_object_generate_events (object, start, end, cb, closure);
}
}
void
calendar_iterate (Calendar *cal, time_t start, time_t end, calendarfn cb, void *closure)
{
calendar_iterate_on_objects (cal->events, start, end, cb, closure);
}
GList *
calendar_get_objects_in_range (GList *objects, time_t start, time_t end, GCompareFunc sort_func)
{
GList *new_events = 0;
for (; objects; objects = objects->next){
iCalObject *object = objects->data;
if ((start <= object->dtstart) && (object->dtend <= end)){
if (sort_func)
new_events = g_list_insert_sorted (new_events, object, sort_func);
else
new_events = g_list_prepend (new_events, object);
}
}
/* Put the list in increasing order if no sort function was specified */
if (!sort_func)
new_events = g_list_reverse (new_events);
return new_events;
}
GList *
calendar_get_todo_in_range (Calendar *cal, time_t start, time_t end, GCompareFunc sort_func)
{
return calendar_get_objects_in_range (cal->todo, start, end, sort_func);
}
GList *
calendar_get_journal_in_range (Calendar *cal, time_t start, time_t end, GCompareFunc sort_func)
{
return calendar_get_objects_in_range (cal->journal, start, end, sort_func);
}
gint
calendar_compare_by_dtstart (gpointer a, gpointer b)
{
iCalObject *obj1, *obj2;
time_t diff;
obj1 = a;
obj2 = b;
diff = obj1->dtstart - obj2->dtstart;
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
#define str_val(obj) (char *) vObjectUStringZValue (obj)
/* Loads our calendar contents from a vObject */
void
calendar_load_from_vobject (Calendar *cal, VObject *vcal)
{
VObjectIterator i;
initPropIterator (&i, vcal);
while (moreIteration (&i)){
VObject *this = nextVObject (&i);
iCalObject *ical;
const char *object_name = vObjectName (this);
if (strcmp (object_name, VCDCreatedProp) == 0){
cal->created = time_from_isodate (str_val (this));
continue;
}
if (strcmp (object_name, VCLocationProp) == 0)
continue; /* FIXME: imlement */
if (strcmp (object_name, VCProdIdProp) == 0)
continue; /* FIXME: implement */
if (strcmp (object_name, VCVersionProp) == 0)
continue; /* FIXME: implement */
if (strcmp (object_name, VCTimeZoneProp) == 0)
continue; /* FIXME: implement */
ical = ical_object_create_from_vobject (this, object_name);
if (ical)
calendar_add_object (cal, ical);
}
}
static void
calendar_set_day (void)
{
time_t calendar_today;
calendar_today = time (NULL);
calendar_day_begin = time_day_begin (calendar_today);
calendar_day_end = time_day_end (calendar_today);
}
/* Loads a calendar from a file */
char *
calendar_load (Calendar *cal, char *fname)
{
VObject *vcal;
struct stat s;
if (cal->filename){
g_warning ("Calendar load called again\n");
return "Internal error";
}
cal->filename = g_strdup (fname);
stat (fname, &s);
cal->file_time = s.st_mtime;
calendar_set_day ();
switch (cal->format) {
case CAL_VCAL:
vcal = Parse_MIME_FromFileName (fname);
if (!vcal)
return "Could not load the calendar";
calendar_load_from_vobject (cal, vcal);
cleanVObject (vcal);
cleanStrTbl ();
break;
#ifdef HAVE_LIBICAL
hi;
case CAL_ICAL:
icalendar_calendar_load (cal, fname);
break;
#endif
default:
return "Unknown calendar format";
}
return NULL;
}
/*
* calendar_load_from_memory:
* @cal: calendar on which we load the information
* @buffer: A buffer that contains a vCalendar file
*
* Loads the information from the vCalendar information in @buffer
* into the Calendar
*/
char *
calendar_load_from_memory (Calendar *cal, const char *buffer)
{
VObject *vcal;
g_return_val_if_fail (buffer != NULL, NULL);
cal->filename = g_strdup ("memory-based-calendar");
vcal = Parse_MIME (buffer, strlen (buffer));
if (!vcal)
return "Could not load the calendar";
cal->file_time = time (NULL);
calendar_load_from_vobject (cal, vcal);
cleanVObject (vcal);
cleanStrTbl ();
return NULL;
}
static VObject *
vcalendar_create_from_calendar (Calendar *cal)
{
VObject *vcal;
GList *l;
time_t now = time (NULL);
struct tm tm;
/* WE call localtime for the side effect of setting tzname */
tm = *localtime (&now);
vcal = newVObject (VCCalProp);
addPropValue (vcal, VCProdIdProp, "-//GNOME//NONSGML GnomeCalendar//EN");
#if defined(HAVE_TM_ZONE)
addPropValue (vcal, VCTimeZoneProp, tm.tm_zone);
#elif defined(HAVE_TZNAME)
addPropValue (vcal, VCTimeZoneProp, tzname[0]);
#endif
addPropValue (vcal, VCVersionProp, VERSION);
cal->temp = vcal;
/* Events */
for (l = cal->events; l; l = l->next) {
VObject *obj;
obj = ical_object_to_vobject ((iCalObject *) l->data);
addVObjectProp (vcal, obj);
}
/* To-do entries */
for (l = cal->todo; l; l = l->next) {
VObject *obj;
obj = ical_object_to_vobject ((iCalObject *) l->data);
addVObjectProp (vcal, obj);
}
return vcal;
}
void
calendar_save (Calendar *cal, char *fname)
{
VObject *vcal;
FILE *fp;
GtkWidget *dlg;
struct stat s;
int status;
if (fname == NULL)
fname = cal->filename;
vcal = vcalendar_create_from_calendar (cal);
if (g_file_exists (fname)){
char *backup_name = g_strconcat (fname, "~", NULL);
if (g_file_exists (backup_name)){
unlink (backup_name);
}
rename (fname, backup_name);
g_free (backup_name);
}
fp = fopen(fname,"w");
if (fp) {
writeVObject(fp, vcal);
fclose(fp);
if (strcmp(cal->filename, fname)) {
if (cal->filename)
g_free (cal->filename);
cal->filename = g_strdup (fname);
}
status = stat (fname, &s);
cal->file_time = s.st_mtime;
} else {
dlg = gnome_message_box_new(_("Failed to save calendar!"),
GNOME_MESSAGE_BOX_ERROR, "Ok", NULL);
gtk_widget_show(dlg);
}
cleanVObject (vcal);
cleanStrTbl ();
}
char *
calendar_get_as_vcal_string (Calendar *cal)
{
VObject *vcal;
char *result;
g_return_val_if_fail (cal != NULL, NULL);
vcal = vcalendar_create_from_calendar (cal);
result = writeMemVObject (NULL, 0, vcal);
cleanVObject (vcal);
cleanStrTbl ();
return result;
}
static gint
calendar_object_compare_by_start (gconstpointer a, gconstpointer b)
{
const CalendarObject *ca = a;
const CalendarObject *cb = b;
time_t diff;
diff = ca->ev_start - cb->ev_start;
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
static int
assemble_event_list (iCalObject *obj, time_t start, time_t end, void *c)
{
CalendarObject *co;
GList **l = c;
co = g_new (CalendarObject, 1);
co->ev_start = start;
co->ev_end = end;
co->ico = obj;
*l = g_list_insert_sorted (*l, co, calendar_object_compare_by_start);
return 1;
}
void
calendar_destroy_event_list (GList *l)
{
GList *p;
for (p = l; p; p = p->next)
g_free (p->data);
g_list_free (l);
}
GList *
calendar_get_events_in_range (Calendar *cal, time_t start, time_t end)
{
GList *l = 0;
calendar_iterate (cal, start, end, assemble_event_list, &l);
return l;
}
void
calendar_object_changed (Calendar *cal, iCalObject *obj, int flags)
{
obj->last_mod = time (NULL);
obj->pilot_status = ICAL_PILOT_SYNC_MOD;
if (!(flags & CHANGE_DATES))
return;
/* Remove any alarms on the alarm list for this object */
while (alarm_kill (obj))
;
ical_object_try_alarms (obj);
}
static void
calendar_day_change (time_t time, CalendarAlarm *which, void *closure)
{
GList *events;
Calendar *cal = closure;
calendar_set_day ();
calendar_init_alarms (cal);
for (events = cal->events; events; events = events->next){
iCalObject *obj = events->data;
ical_object_try_alarms (obj);
}
}
static void
calendar_init_alarms (Calendar *cal)
{
CalendarAlarm day_change_alarm;
day_change_alarm.trigger = calendar_day_end;
alarm_add (&day_change_alarm, calendar_day_change, cal);
}
static iCalObject *
calendar_object_find_in_list (Calendar *cal, GList *list, const char *uid)
{
GList *l;
for (l = list; l; l = l->next){
iCalObject *obj = l->data;
if (strcmp (obj->uid, uid) == 0)
return obj;
}
return NULL;
}
iCalObject *
calendar_object_find_event (Calendar *cal, const char *uid)
{
g_return_val_if_fail (cal != NULL, NULL);
g_return_val_if_fail (uid != NULL, NULL);
return g_hash_table_lookup (cal->event_hash, uid);
}
iCalObject *
calendar_object_find_todo (Calendar *cal, const char *uid)
{
g_return_val_if_fail (cal != NULL, NULL);
g_return_val_if_fail (uid != NULL, NULL);
return calendar_object_find_in_list (cal, cal->todo, uid);
}
iCalObject *
calendar_object_find (Calendar *cal, const char *uid)
{
iCalObject *obj;
g_return_val_if_fail (cal != NULL, NULL);
g_return_val_if_fail (uid != NULL, NULL);
obj = calendar_object_find_in_list (cal, cal->todo, uid);
if (obj == NULL)
obj = calendar_object_find_in_list (cal, cal->events, uid);
return obj;
}
iCalObject *
calendar_object_find_by_pilot (Calendar *cal, int pilot_id)
{
GList *l;
g_return_val_if_fail (cal != NULL, NULL);
for (l = cal->events; l; l = l->next){
iCalObject *obj = l->data;
if (obj->pilot_id == pilot_id)
return obj;
}
for (l = cal->todo; l; l = l->next){
iCalObject *obj = l->data;
if (obj->pilot_id == pilot_id)
return obj;
}
return NULL;
}
/*
* calendar_string_from_object:
*
* Returns the iCalObject @object armored around a vCalendar
* object as a string.
*/
char *
calendar_string_from_object (iCalObject *object)
{
Calendar *cal;
char *str;
g_return_val_if_fail (object != NULL, NULL);
cal = calendar_new ("Temporal",CALENDAR_INIT_NIL);
calendar_add_object (cal, object);
str = calendar_get_as_vcal_string (cal);
calendar_remove_object (cal, object);
calendar_destroy (cal);
return str;
}