/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Calendar objects implementations. * Copyright (C) 1998 the Free Software Foundation * * Authors: * Miguel de Icaza (miguel@gnu.org) * Federico Mena (quartic@gimp.org) */ #include #include #include #include #include #include #include "calobj.h" #include "timeutil.h" #include "libversit/vcc.h" #include "icalendar-save.h" #include "icalendar.h" /* VCalendar product ID */ #define PRODID "-//Helix Code//NONSGML Evolution Calendar//EN" static gint compare_exdates (gconstpointer a, gconstpointer b); static void ical_object_normalize_summary (iCalObject *ico); static void list_free (GList *list); char * ical_gen_uid (void) { static char *hostname; time_t t = time (NULL); static int serial; if (!hostname){ char buffer [128]; if ((gethostname (buffer, sizeof (buffer)-1) == 0) && (buffer [0] != 0)) hostname = g_strdup (buffer); else hostname = g_strdup ("localhost"); } return g_strdup_printf ( "%s-%d-%d-%d-%d@%s", isodate_from_time_t (t), getpid (), getgid (), getppid (), serial++, hostname); } iCalObject * ical_object_new (void) { iCalObject *ico; ico = g_new0 (iCalObject, 1); ico->seq = -1; ico->dtstamp = time (NULL); ico->uid = ical_gen_uid (); ico->pilot_id = 0; ico->pilot_status = ICAL_PILOT_SYNC_MOD; ico->ref_count = 1; return ico; } iCalObject * ical_new (char *comment, char *organizer, char *summary) { iCalObject *ico; ico = ical_object_new (); ico->comment = g_strdup (comment); ico->organizer = g_new0 (iCalPerson, 1); ico->organizer->addr = g_strdup (organizer); ico->summary = g_strdup (summary); ico->class = g_strdup ("PUBLIC"); ico->status = g_strdup ("NEEDS ACTION"); ico->dalarm.type = ALARM_DISPLAY; ico->palarm.type = ALARM_PROGRAM; ico->malarm.type = ALARM_MAIL; ico->aalarm.type = ALARM_AUDIO; ical_object_normalize_summary (ico); return ico; } void ical_object_ref (iCalObject *ico) { ico->ref_count++; } #define free_if_defined(x) if (x){ g_free (x); x = 0; } #define lfree_if_defined(x) if (x){ list_free (x); x = 0; } static void ical_object_destroy (iCalObject *ico) { /* Regular strings */ free_if_defined (ico->comment); free_if_defined (ico->organizer); free_if_defined (ico->summary); free_if_defined (ico->uid); free_if_defined (ico->status); free_if_defined (ico->class); free_if_defined (ico->url); free_if_defined (ico->recur); /* Lists */ lfree_if_defined (ico->exdate); lfree_if_defined (ico->categories); lfree_if_defined (ico->resources); lfree_if_defined (ico->related); lfree_if_defined (ico->attach); /* Alarms */ g_free (ico->dalarm.data); g_free (ico->palarm.data); g_free (ico->malarm.data); g_free (ico->aalarm.data); g_free (ico); } void ical_object_unref (iCalObject *ico) { ico->ref_count--; if (ico->ref_count == 0) ical_object_destroy (ico); } static void my_free (gpointer data, gpointer user_dat_ignored) { g_free (data); } static void list_free (GList *list) { g_list_foreach (list, my_free, 0); g_list_free (list); } /* This resets any recurrence rules of the iCalObject. */ void ical_object_reset_recurrence (iCalObject *ico) { free_if_defined (ico->recur); lfree_if_defined (ico->exdate); } static GList * set_list (char *str) { GList *list = 0; char *s; for (s = strtok (str, ";"); s; s = strtok (NULL, ";")) list = g_list_prepend (list, g_strdup (s)); return list; } static GList * set_date_list (char *str) { GList *list = 0; char *s; for (s = strtok (str, ";,"); s; s = strtok (NULL, ";,")){ time_t *t = g_new (time_t, 1); while (*s && isspace (*s)) s++; *t = time_from_isodate (s); list = g_list_prepend (list, t); } return list; } void ical_object_add_exdate (iCalObject *o, time_t t) { time_t *pt = g_new (time_t, 1); *pt = t; o->exdate = g_list_prepend (o->exdate, pt); } static void ignore_space(char **str) { while (**str && isspace (**str)) (*str)++; } static void skip_numbers (char **str) { while (**str){ ignore_space (str); if (!isdigit (**str)) return; while (**str && isdigit (**str)) (*str)++; } } static void weekdaylist (iCalObject *o, char **str) { int i; struct { char first_letter, second_letter; int index; } days [] = { { 'S', 'U', 0 }, { 'M', 'O', 1 }, { 'T', 'U', 2 }, { 'W', 'E', 3 }, { 'T', 'H', 4 }, { 'F', 'R', 5 }, { 'S', 'A', 6 } }; ignore_space (str); do { for (i = 0; i < 7; i++){ if (**str == days [i].first_letter && *(*str+1) == days [i].second_letter){ o->recur->weekday |= 1 << i; *str += 2; if (**str == ' ') (*str)++; } } } while (isalpha ((unsigned char) **str)); if (o->recur->weekday == 0){ struct tm tm = *localtime (&o->dtstart); o->recur->weekday = 1 << tm.tm_wday; } } static void weekdaynum (iCalObject *o, char **str) { int i; struct { char first_letter, second_letter; int index; } days [] = { { 'S', 'U', 0 }, { 'M', 'O', 1 }, { 'T', 'U', 2 }, { 'W', 'E', 3 }, { 'T', 'H', 4 }, { 'F', 'R', 5 }, { 'S', 'A', 6 } }; ignore_space (str); do { for (i = 0; i < 7; i++){ if (**str == days [i].first_letter && *(*str+1) == days [i].second_letter){ o->recur->weekday = i; *str += 2; if (**str == ' ') (*str)++; } } } while (isalpha ((unsigned char) **str)); } static void ocurrencelist (iCalObject *o, char **str) { char *p; ignore_space (str); p = *str; if (!isdigit (*p)) return; if (!(*p >= '1' && *p <= '5')) return; if (!(*(p+1) == '+' || *(p+1) == '-')) return; o->recur->u.month_pos = (*p-'0') * (*(p+1) == '+' ? 1 : -1); *str += 2; } #if 0 static void daynumber (iCalObject *o, char **str) { int val = 0; char *p = *str; ignore_space (str); if (strcmp (p, "LD")){ o->recur->u.month_day = DAY_LASTDAY; *str += 2; return; } if (!(isdigit (*p))) return; while (**str && isdigit (**str)){ val = val * 10 + (**str - '0'); (*str)++; } if (**str == '+') (*str)++; if (**str == '-') val *= -1; o->recur->u.month_day = val; } #endif static void daynumberlist (iCalObject *o, char **str) { int first = 0; int val = 0; ignore_space (str); while (**str){ if (!isdigit (**str)) return; while (**str && isdigit (**str)){ val = 10 * val + (**str - '0'); (*str)++; } if (!first){ /* * Some broken applications set this to zero */ if (val == 0){ struct tm day = *localtime (&o->dtstart); val = day.tm_mday; } o->recur->u.month_day = val; first = 1; val = 0; } } } static void load_recur_weekly (iCalObject *o, char **str) { weekdaylist (o, str); } static void load_recur_monthly_pos (iCalObject *o, char **str) { ocurrencelist (o, str); weekdaynum (o, str); } static void load_recur_monthly_day (iCalObject *o, char **str) { daynumberlist (o, str); } static void load_recur_yearly_month (iCalObject *o, char **str) { /* Skip as we do not support multiple months and we do expect * the dtstart to agree with the value on this field */ skip_numbers (str); } static void load_recur_yearly_day (iCalObject *o, char **str) { /* Skip as we do not support multiple days and we do expect * the dtstart to agree with the value on this field * * FIXME: we should support every-n-years */ skip_numbers (str); } static void duration (iCalObject *o, char **str) { unsigned int duration = 0; ignore_space (str); if (**str != '#') return; (*str)++; while (**str && isdigit (**str)){ duration = duration * 10 + (**str - '0'); (*str)++; } o->recur->duration = duration; } static void enddate (iCalObject *o, char **str) { ignore_space (str); if (isdigit (**str)){ o->recur->_enddate = time_from_isodate (*str); *str += 16; } } static int load_recurrence (iCalObject *o, char *str) { enum RecurType type; int interval = 0; type = -1; switch (*str++){ case 'D': type = RECUR_DAILY; break; case 'W': type = RECUR_WEEKLY; break; case 'M': if (*str == 'P') type = RECUR_MONTHLY_BY_POS; else if (*str == 'D') type = RECUR_MONTHLY_BY_DAY; str++; break; case 'Y': if (*str == 'M') type = RECUR_YEARLY_BY_MONTH; else if (*str == 'D') type = RECUR_YEARLY_BY_DAY; str++; break; } if (type == -1) return 0; o->recur = g_new0 (Recurrence, 1); o->recur->type = type; ignore_space (&str); /* Get the interval */ for (;*str && isdigit (*str);str++) interval = interval * 10 + (*str-'0'); if (interval == 0) interval = 1; o->recur->interval = interval; /* this is the default per the spec */ o->recur->duration = 2; ignore_space (&str); switch (type){ case RECUR_DAILY: break; case RECUR_WEEKLY: load_recur_weekly (o, &str); break; case RECUR_MONTHLY_BY_POS: load_recur_monthly_pos (o, &str); break; case RECUR_MONTHLY_BY_DAY: load_recur_monthly_day (o, &str); break; case RECUR_YEARLY_BY_MONTH: load_recur_yearly_month (o, &str); break; case RECUR_YEARLY_BY_DAY: load_recur_yearly_day (o, &str); break; default: g_warning ("Unimplemented recurrence type %d", (int) type); break; } duration (o, &str); enddate (o, &str); /* Compute the enddate */ if (o->recur->_enddate == 0){ if (o->recur->duration != 0){ ical_object_compute_end (o); } else o->recur->enddate = 0; } else { o->recur->enddate = o->recur->_enddate; } return 1; } #define is_a_prop_of(obj,prop) isAPropertyOf (obj,prop) #define str_val(obj) the_str = fakeCString (vObjectUStringZValue (obj)) #define has(obj,prop) (vo = isAPropertyOf (obj, prop)) /* * FIXME: This is loosing precission. Enhanec the thresholds */ #define HOURS(n) (n*(60*60)) static void setup_alarm_at (iCalObject *ico, CalendarAlarm *alarm, char *iso_time, VObject *vo) { time_t alarm_time = time_from_isodate (iso_time); time_t base = ico->dtstart; int d = difftime (base, alarm_time); VObject *a; char *the_str; alarm->enabled = 1; if (d > HOURS (2)){ if (d > HOURS (48)){ alarm->count = d / HOURS (24); alarm->units = ALARM_DAYS; } else { alarm->count = d / (60*60); alarm->units = ALARM_HOURS; } } else { alarm->count = d / 60; alarm->units = ALARM_MINUTES; } if ((a = is_a_prop_of (vo, VCSnoozeTimeProp))){ alarm->snooze_secs = isodiff_to_secs (str_val (a)); free (the_str); } if ((a = is_a_prop_of (vo, VCRepeatCountProp))){ alarm->snooze_repeat = atoi (str_val (a)); free (the_str); } } /* * Duplicates an iCalObject. Implementation is a grand hack. * If you need the new ICalObject to have a new uid, free the current one, * and call ical_gen_uid() to generate a new one. */ iCalObject * ical_object_duplicate (iCalObject *o) { VObject *vo; iCalObject *new; vo = ical_object_to_vobject (o); switch (o->type){ case ICAL_EVENT: new = ical_object_create_from_vobject (vo, VCEventProp); break; case ICAL_TODO: new = ical_object_create_from_vobject (vo, VCTodoProp); break; default: new = NULL; } cleanVObject (vo); return new; } /* FIXME: we need to load the recurrence properties */ iCalObject * ical_object_create_from_vobject (VObject *o, const char *object_name) { time_t now = time (NULL); iCalObject *ical; VObject *vo, *a; VObjectIterator i; char *the_str; ical = g_new0 (iCalObject, 1); if (strcmp (object_name, VCEventProp) == 0) ical->type = ICAL_EVENT; else if (strcmp (object_name, VCTodoProp) == 0) ical->type = ICAL_TODO; else { g_free (ical); return 0; } ical->ref_count = 1; /* uid */ if (has (o, VCUniqueStringProp)){ ical->uid = g_strdup (str_val (vo)); free (the_str); } else { ical->uid = ical_gen_uid (); } /* seq */ if (has (o, VCSequenceProp)){ ical->seq = atoi (str_val (vo)); free (the_str); } else ical->seq = 0; /* dtstart */ if (has (o, VCDTstartProp)){ ical->dtstart = time_from_isodate (str_val (vo)); free (the_str); } else ical->dtstart = 0; /* dtend */ ical->dtend = 0; /* default value */ if (ical->type == ICAL_EVENT){ if (has (o, VCDTendProp)){ ical->dtend = time_from_isodate (str_val (vo)); free (the_str); } } else if (ical->type == ICAL_TODO){ if (has (o, VCDueProp)){ ical->dtend = time_from_isodate (str_val (vo)); free (the_str); } } /* dcreated */ if (has (o, VCDCreatedProp)){ ical->created = time_from_isodate (str_val (vo)); free (the_str); } /* completed */ if (has (o, VCCompletedProp)){ ical->completed = time_from_isodate (str_val (vo)); free (the_str); } /* last_mod */ if (has (o, VCLastModifiedProp)){ ical->last_mod = time_from_isodate (str_val (vo)); free (the_str); } else ical->last_mod = now; /* exdate */ if (has (o, VCExpDateProp)){ ical->exdate = set_date_list (str_val (vo)); free (the_str); } /* description/comment */ if (has (o, VCDescriptionProp)){ ical->comment = g_strdup (str_val (vo)); free (the_str); } /* summary */ if (has (o, VCSummaryProp)){ ical->summary = g_strdup (str_val (vo)); free (the_str); /* Convert any CR/LF/CRLF sequences in the summary field to spaces so we just have a one-line field. */ ical_object_normalize_summary (ical); } else ical->summary = g_strdup (""); /* status */ if (has (o, VCStatusProp)){ ical->status = g_strdup (str_val (vo)); free (the_str); } else ical->status = g_strdup ("NEEDS ACTION"); if (has (o, VCClassProp)){ ical->class = g_strdup (str_val (vo)); free (the_str); } else ical->class = g_strdup ("PUBLIC"); /* categories */ if (has (o, VCCategoriesProp)){ ical->categories = set_list (str_val (vo)); free (the_str); } /* resources */ if (has (o, VCResourcesProp)){ ical->resources = set_list (str_val (vo)); free (the_str); } /* priority */ if (has (o, VCPriorityProp)){ ical->priority = atoi (str_val (vo)); free (the_str); } /* tranparency */ if (has (o, VCTranspProp)){ ical->transp = atoi (str_val (vo)) ? ICAL_TRANSPARENT : ICAL_OPAQUE; free (the_str); } /* Organizer */ if (has (o, VCOrgNameProp)){ ical->organizer = g_new0 (iCalPerson, 1); ical->organizer->addr = g_strdup (str_val (vo)); free (the_str); } /* related */ if (has (o, VCRelatedToProp)){ char *str; char *s; iCalRelation *rel; str = str_val (vo); for (s = strtok (str, ";"); s; s = strtok (NULL, ";")) { rel = g_new0 (iCalRelation, 1); rel->uid = g_strdup (s); rel->reltype = g_strdup ("PARENT"); ical->related = g_list_prepend (ical->related, rel); } free (the_str); } /* attach */ initPropIterator (&i, o); while (moreIteration (&i)){ vo = nextVObject (&i); if (strcmp (vObjectName (vo), VCAttachProp) == 0){ ical->attach = g_list_prepend (ical->attach, g_strdup (str_val (vo))); free (the_str); } } /* url */ if (has (o, VCURLProp)){ /* There seems to be a problem with the URL property. For some reason an empty property gets saved, vObjectUStringZValue returns NULL and fakeCString crashes. So we check for NULL. */ const wchar_t *zval; zval = vObjectUStringZValue (o); if (zval) { the_str = fakeCString (zval); ical->url = g_strdup (the_str); free (the_str); } } /* dalarm */ ical->dalarm.type = ALARM_DISPLAY; ical->dalarm.enabled = 0; if (has (o, VCDAlarmProp)){ if ((a = is_a_prop_of (vo, VCRunTimeProp))){ setup_alarm_at (ical, &ical->dalarm, str_val (a), vo); free (the_str); } } /* aalarm */ ical->aalarm.type = ALARM_AUDIO; ical->aalarm.enabled = 0; if (has (o, VCAAlarmProp)){ if ((a = is_a_prop_of (vo, VCRunTimeProp))){ setup_alarm_at (ical, &ical->aalarm, str_val (a), vo); free (the_str); } } /* palarm */ ical->palarm.type = ALARM_PROGRAM; ical->palarm.enabled = 0; if (has (o, VCPAlarmProp)){ ical->palarm.type = ALARM_PROGRAM; if ((a = is_a_prop_of (vo, VCRunTimeProp))){ setup_alarm_at (ical, &ical->palarm, str_val (a), vo); free (the_str); if ((a = is_a_prop_of (vo, VCProcedureNameProp))){ ical->palarm.data = g_strdup (str_val (a)); free (the_str); } else ical->palarm.data = g_strdup (""); } } /* malarm */ ical->malarm.type = ALARM_MAIL; ical->malarm.enabled = 0; if (has (o, VCMAlarmProp)){ ical->malarm.type = ALARM_MAIL; if ((a = is_a_prop_of (vo, VCRunTimeProp))){ setup_alarm_at (ical, &ical->malarm, str_val (a), vo); free (the_str); if ((a = is_a_prop_of (vo, VCEmailAddressProp))){ ical->malarm.data = g_strdup (str_val (a)); free (the_str); } else ical->malarm.data = g_strdup (""); } } /* rrule */ if (has (o, VCRRuleProp)){ if (!load_recurrence (ical, str_val (vo))) { ical_object_unref (ical); return NULL; } free (the_str); } /* * Pilot */ if (has (o, XPilotIdProp)){ ical->pilot_id = atoi (str_val (vo)); free (the_str); } else ical->pilot_id = 0; if (has (o, XPilotStatusProp)){ ical->pilot_status = atoi (str_val (vo)); free (the_str); } else ical->pilot_status = ICAL_PILOT_SYNC_MOD; return ical; } static char * to_str (int num) { static char buf [40]; sprintf (buf, "%d", num); return buf; } /* * stores a GList in the property. */ static void store_list (VObject *o, char *prop, GList *values) { GList *l; int len; char *result, *p; for (len = 0, l = values; l; l = l->next) len += strlen (l->data) + 1; result = g_malloc (len); for (p = result, l = values; l; l = l->next) { int len = strlen (l->data); strcpy (p, l->data); if (l->next) { p [len] = ';'; p += len+1; } else p += len; } *p = 0; addPropValue (o, prop, result); g_free (result); } static void store_rel_list (VObject *o, char *prop, GList *values) { GList *l; int len; char *result, *p; for (len = 0, l = values; l; l = l->next) len += strlen (((iCalRelation*)(l->data))->uid) + 1; result = g_malloc (len); for (p = result, l = values; l; l = l->next) { int len = strlen (((iCalRelation*)(l->data))->uid); strcpy (p, ((iCalRelation*)(l->data))->uid); if (l->next) { p [len] = ';'; p += len+1; } else p += len; } *p = 0; addPropValue (o, prop, result); g_free (result); } static void store_date_list (VObject *o, char *prop, GList *values) { GList *l; int size, len; char *s, *p; size = g_list_length (values); s = p = g_malloc ((size * 17 + 1) * sizeof (char)); for (l = values; l; l = l->next){ strcpy (s, isodate_from_time_t (*(time_t *)l->data)); len = strlen (s); s [len] = ','; s += len + 1; } s--; *s = 0; addPropValue (o, prop, p); g_free (p); } static char *recur_type_name [] = { "D", "W", "MP", "MD", "YM", "YD" }; static char *recur_day_list [] = { "SU", "MO", "TU","WE", "TH", "FR", "SA" }; static char *alarm_names [] = { VCMAlarmProp, VCPAlarmProp, VCDAlarmProp, VCAAlarmProp }; static VObject * save_alarm (VObject *o, CalendarAlarm *alarm, iCalObject *ical) { VObject *alarm_object; struct tm tm; time_t alarm_time; if (!alarm->enabled) return NULL; tm = *localtime (&ical->dtstart); switch (alarm->units){ case ALARM_MINUTES: tm.tm_min -= alarm->count; break; case ALARM_HOURS: tm.tm_hour -= alarm->count; break; case ALARM_DAYS: tm.tm_mday -= alarm->count; break; } alarm_time = mktime (&tm); alarm_object = addProp (o, alarm_names [alarm->type]); addPropValue (alarm_object, VCRunTimeProp, isodate_from_time_t (alarm_time)); if (alarm->snooze_secs) addPropValue (alarm_object, VCSnoozeTimeProp, isodiff_from_secs (alarm->snooze_secs)); else addPropValue (alarm_object, VCSnoozeTimeProp, ""); if (alarm->snooze_repeat){ char buf [20]; sprintf (buf, "%d", alarm->snooze_repeat); addPropValue (alarm_object, VCRepeatCountProp, buf); } else addPropValue (alarm_object, VCRepeatCountProp, ""); return alarm_object; } VObject * ical_object_to_vobject (iCalObject *ical) { VObject *o, *alarm, *s; GList *l; if (ical->type == ICAL_EVENT) o = newVObject (VCEventProp); else o = newVObject (VCTodoProp); /* uid */ if (ical->uid) addPropValue (o, VCUniqueStringProp, ical->uid); /* seq */ addPropValue (o, VCSequenceProp, to_str (ical->seq)); /* dtstart */ addPropValue (o, VCDTstartProp, isodate_from_time_t (ical->dtstart)); /* dtend */ if (ical->type == ICAL_EVENT){ addPropValue (o, VCDTendProp, isodate_from_time_t (ical->dtend)); } else if (ical->type == ICAL_TODO){ addPropValue (o, VCDueProp, isodate_from_time_t (ical->dtend)); } /* dcreated */ addPropValue (o, VCDCreatedProp, isodate_from_time_t (ical->created)); /* completed */ if (ical->completed) addPropValue (o, VCDTendProp, isodate_from_time_t (ical->completed)); /* last_mod */ addPropValue (o, VCLastModifiedProp, isodate_from_time_t (ical->last_mod)); /* exdate */ if (ical->exdate) store_date_list (o, VCExpDateProp, ical->exdate); /* description/comment */ if (ical->comment && strlen (ical->comment)){ s = addPropValue (o, VCDescriptionProp, ical->comment); if (strchr (ical->comment, '\n')) addProp (s, VCQuotedPrintableProp); } /* summary */ if (ical->summary && strlen (ical->summary)) { s = addPropValue (o, VCSummaryProp, ical->summary); if (strchr (ical->summary, '\n')) addProp (s, VCQuotedPrintableProp); } /* status */ addPropValue (o, VCStatusProp, ical->status); /* class */ addPropValue (o, VCClassProp, ical->class); /* categories */ if (ical->categories) store_list (o, VCCategoriesProp, ical->categories); /* resources */ if (ical->resources) store_list (o, VCCategoriesProp, ical->resources); /* priority */ addPropValue (o, VCPriorityProp, to_str (ical->priority)); /* transparency */ addPropValue (o, VCTranspProp, to_str (ical->transp)); /* Owner/organizer */ if (ical->organizer && ical->organizer->addr) addPropValue (o, VCOrgNameProp, ical->organizer->addr); /* related */ if (ical->related) store_rel_list (o, VCRelatedToProp, ical->related); /* attach */ for (l = ical->attach; l; l = l->next) addPropValue (o, VCAttachProp, l->data); /* url */ if (ical->url) addPropValue (o, VCURLProp, ical->url); if (ical->recur){ char result [256]; char buffer [80]; int i; sprintf (result, "%s%d ", recur_type_name [ical->recur->type], ical->recur->interval); switch (ical->recur->type){ case RECUR_DAILY: break; case RECUR_WEEKLY: for (i = 0; i < 7; i++){ if (ical->recur->weekday & (1 << i)){ sprintf (buffer, "%s ", recur_day_list [i]); strcat (result, buffer); } } break; case RECUR_MONTHLY_BY_POS: { int nega = ical->recur->u.month_pos < 0; sprintf (buffer, "%d%s ", nega ? -ical->recur->u.month_pos : ical->recur->u.month_pos, nega ? "-" : "+"); strcat (result, buffer); /* the gui is set up for a single day, not a set here in this case */ sprintf (buffer, "%s ", recur_day_list [ical->recur->weekday]); strcat (result, buffer); } break; case RECUR_MONTHLY_BY_DAY: sprintf (buffer, "%d ", ical->recur->u.month_pos); strcat (result, buffer); break; case RECUR_YEARLY_BY_MONTH: break; case RECUR_YEARLY_BY_DAY: break; } if (ical->recur->_enddate == 0) sprintf (buffer, "#%d ",ical->recur->duration); else sprintf (buffer, "%s ", isodate_from_time_t (ical->recur->_enddate)); strcat (result, buffer); addPropValue (o, VCRRuleProp, result); } save_alarm (o, &ical->aalarm, ical); save_alarm (o, &ical->dalarm, ical); if ((alarm = save_alarm (o, &ical->palarm, ical))) addPropValue (alarm, VCProcedureNameProp, ical->palarm.data); if ((alarm = save_alarm (o, &ical->malarm, ical))) addPropValue (alarm, VCEmailAddressProp, ical->malarm.data); /* Pilot */ { char buffer [20]; sprintf (buffer, "%d", ical->pilot_id); addPropValue (o, XPilotIdProp, buffer); sprintf (buffer, "%d", ical->pilot_status); addPropValue (o, XPilotStatusProp, buffer); } return o; } void ical_foreach (GList *events, calendarfn fn, void *closure) { for (; events; events = events->next){ iCalObject *ical = events->data; (*fn) (ical, ical->dtstart, ical->dtend, closure); } } static int is_date_in_list (GList *list, struct tm *date) { struct tm tm; for (; list; list = list->next){ time_t *timep = list->data; tm = *localtime (timep); if (date->tm_mday == tm.tm_mday && date->tm_mon == tm.tm_mon && date->tm_year == tm.tm_year){ return 1; } } return 0; } /* Generates an event instance based on the reference time */ static gboolean generate (iCalObject *ico, time_t reference, calendarfn cb, void *closure) { time_t offset; struct tm tm_start, ref; time_t start, end; offset = ico->dtend - ico->dtstart; tm_start = *localtime (&ico->dtstart); ref = *localtime (&reference); tm_start.tm_mday = ref.tm_mday; tm_start.tm_mon = ref.tm_mon; tm_start.tm_year = ref.tm_year; start = mktime (&tm_start); if (start == -1) { g_message ("generate(): Produced invalid start date!"); return FALSE; } end = start + offset; #if 0 /* FIXME: I think this is not needed, since we are offsetting by full day values, * and the times should remain the same --- if you have a daily appointment * at 18:00, it is always at 18:00 even during daylight savings. * * However, what should happen on the exact change-of-savings day with * appointments in the early morning hours? */ if (ref.tm_isdst > tm_start.tm_isdst) { tm_start.tm_hour--; tm_end.tm_hour--; } else if (ref.tm_isdst < tm_start.tm_isdst) { tm_start.tm_hour++; tm_end.tm_hour++; } #endif if (ico->exdate && is_date_in_list (ico->exdate, &tm_start)) return TRUE; return (*cb) (ico, start, end, closure); } int ical_object_get_first_weekday (int weekday_mask) { int i; for (i = 0; i < 7; i++) if (weekday_mask & (1 << i)) return i; return -1; } #define time_in_range(t, a, b) ((t >= a) && (b ? (t < b) : 1)) #define recur_in_range(t, r) (r->enddate ? (t < r->enddate) : 1) /* * Generate every possible event. Invokes the callback routine for * every occurrence of the event in the [START, END] time interval. * * If END is zero, the event is generated forever. * The callback routine is expected to return 0 when no further event * generation is requested. */ void ical_object_generate_events (iCalObject *ico, time_t start, time_t end, calendarfn cb, void *closure) { time_t current; int first_week_day; /* If there is no recurrence, just check ranges */ if (!ico->recur) { if ((end && (ico->dtstart < end) && (ico->dtend > start)) || ((end == 0) && (ico->dtend > start))) { /* The new calendar views expect the times to not be clipped, so they can show that it continues past the end of the viewable area. */ #if 0 time_t ev_s, ev_e; /* Clip range */ ev_s = MAX (ico->dtstart, start); ev_e = MIN (ico->dtend, end); (* cb) (ico, ev_s, ev_e, closure); #else (* cb) (ico, ico->dtstart, ico->dtend, closure); #endif } return; } /* The event has a recurrence rule -- check that we will generate at least one instance */ if (end != 0) { if (ico->dtstart > end) return; if (!IS_INFINITE (ico->recur) && (ico->recur->enddate < start)) return; } /* Generate the instances */ current = ico->dtstart; switch (ico->recur->type) { case RECUR_DAILY: do { if (time_in_range (current, start, end) && recur_in_range (current, ico->recur)) if (!generate (ico, current, cb, closure)) return; /* Advance */ current = time_add_day (current, ico->recur->interval); if (current == -1) { g_warning ("RECUR_DAILY: time_add_day() returned invalid time"); return; } } while ((current < end) || (end == 0)); break; case RECUR_WEEKLY: do { struct tm tm; tm = *localtime (¤t); if (time_in_range (current, start, end) && recur_in_range (current, ico->recur)) { /* Weekdays to recur on are specified as a bitmask */ if (ico->recur->weekday & (1 << tm.tm_wday)) { if (!generate (ico, current, cb, closure)) return; } } /* Advance by day for scanning the week or by interval at week end */ if (tm.tm_wday == 6) current = time_add_day (current, (ico->recur->interval - 1) * 7 + 1); else current = time_add_day (current, 1); if (current == -1) { g_warning ("RECUR_WEEKLY: time_add_day() returned invalid time\n"); return; } } while (current < end || (end == 0)); break; case RECUR_MONTHLY_BY_POS: /* FIXME: We only deal with positives now */ if (ico->recur->u.month_pos < 0) { g_warning ("RECUR_MONTHLY_BY_POS does not support negative positions yet"); return; } if (ico->recur->u.month_pos == 0) return; first_week_day = /* ical_object_get_first_weekday (ico->recur->weekday); */ ico->recur->weekday; /* the i/f only lets you choose a single day of the week! */ /* This should not happen, but take it into account */ if (first_week_day == -1) { g_warning ("ical_object_get_first_weekday() returned -1"); return; } do { struct tm tm; time_t t; int week_day_start; tm = *localtime (¤t); tm.tm_mday = 1; t = mktime (&tm); tm = *localtime (&t); week_day_start = tm.tm_wday; tm.tm_mday = (7 * (ico->recur->u.month_pos - ((week_day_start <= first_week_day ) ? 1 : 0)) - (week_day_start - first_week_day) + 1); if( tm.tm_mday > 31 ) { tm.tm_mday = 1; tm.tm_mon += ico->recur->interval; current = mktime (&tm); continue; } switch( tm.tm_mon ) { case 3: case 5: case 8: case 10: if( tm.tm_mday > 30 ) { tm.tm_mday = 1; tm.tm_mon += ico->recur->interval; current = mktime (&tm); continue; } break; case 1: if( ((tm.tm_year+1900)%4) == 0 && ((tm.tm_year+1900)%400) != 100 && ((tm.tm_year+1900)%400) != 200 && ((tm.tm_year+1900)%400) != 300 ) { if( tm.tm_mday > 29 ) { tm.tm_mday = 1; tm.tm_mon += ico->recur->interval; current = mktime (&tm); continue; } } else { if( tm.tm_mday > 28 ) { tm.tm_mday = 1; tm.tm_mon += ico->recur->interval; current = mktime (&tm); continue; } } break; } t = mktime (&tm); if (time_in_range (t, start, end) && recur_in_range (current, ico->recur)) if (!generate (ico, t, cb, closure)) return; /* Advance by the appropriate number of months */ current = mktime (&tm); tm.tm_mday = 1; tm.tm_mon += ico->recur->interval; current = mktime (&tm); if (current == -1) { g_warning ("RECUR_MONTHLY_BY_DAY: mktime error\n"); return; } } while ((current < end) || (end == 0)); break; case RECUR_MONTHLY_BY_DAY: do { struct tm tm; time_t t; int p; tm = *localtime (¤t); p = tm.tm_mday; tm.tm_mday = ico->recur->u.month_day; t = mktime (&tm); if (time_in_range (t, start, end) && recur_in_range (current, ico->recur)) if (!generate (ico, t, cb, closure)) return; /* Advance by the appropriate number of months */ tm.tm_mday = p; tm.tm_mon += ico->recur->interval; current = mktime (&tm); if (current == -1) { g_warning ("RECUR_MONTHLY_BY_DAY: mktime error\n"); return; } } while (current < end || (end == 0)); break; case RECUR_YEARLY_BY_MONTH: case RECUR_YEARLY_BY_DAY: do { if (time_in_range (current, start, end) && recur_in_range (current, ico->recur)) if (!generate (ico, current, cb, closure)) return; /* Advance */ current = time_add_year (current, ico->recur->interval); } while (current < end || (end == 0)); break; default: g_assert_not_reached (); } } static int duration_callback (iCalObject *ico, time_t start, time_t end, void *closure) { int *count = closure; struct tm tm; tm = *localtime (&start); (*count)++; if (ico->recur->duration == *count) { ico->recur->enddate = time_day_end (end); return 0; } return 1; } /* Computes ico->recur->enddate from ico->recur->duration */ void ical_object_compute_end (iCalObject *ico) { int count = 0; g_return_if_fail (ico->recur != NULL); ico->recur->_enddate = 0; ico->recur->enddate = 0; ical_object_generate_events (ico, ico->dtstart, 0, duration_callback, &count); } int alarm_compute_offset (CalendarAlarm *a) { if (!a->enabled) return -1; switch (a->units){ case ALARM_MINUTES: a->offset = a->count * 60; break; case ALARM_HOURS: a->offset = a->count * 3600; break; case ALARM_DAYS: a->offset = a->count * 24 * 3600; } return a->offset; } /** * ical_object_find_in_string: * @uid: Unique identifier of the sought object. * @vcalobj: String representation of a complete calendar object. * @ico: The resulting #iCalObject is stored here. * * Parses a complete vCalendar object string and tries to find the calendar * object that matches the specified @uid. If found, it stores the resulting * #iCalObject in the @ico parameter. * * Return value: A result code depending on whether the parse and search were * successful. **/ CalObjFindStatus ical_object_find_in_string (const char *uid, const char *vcalobj, iCalObject **ico) { #if 0 icalcomponent* comp = NULL; icalcomponent *subcomp; iCalObject *ical; g_return_val_if_fail (vcalobj != NULL, CAL_OBJ_FIND_NOT_FOUND); comp = icalparser_parse_string (vcalobj); if (!comp) { printf ("CAL_OBJ_FIND_SYNTAX_ERROR #1\n"); return CAL_OBJ_FIND_SYNTAX_ERROR; } subcomp = icalcomponent_get_first_component (comp, ICAL_ANY_COMPONENT); if (!subcomp) { printf ("CAL_OBJ_FIND_SYNTAX_ERROR #2\n"); return CAL_OBJ_FIND_SYNTAX_ERROR; } while (subcomp) { ical = ical_object_create_from_icalcomponent (subcomp); if (ical->type != ICAL_EVENT && ical->type != ICAL_TODO && ical->type != ICAL_JOURNAL) { g_warning ("Skipping unsupported iCalendar component"); } else { if (strcasecmp (ical->uid, uid) == 0) { (*ico) = ical; (*ico)->ref_count = 1; printf ("CAL_OBJ_FIND_SUCCESS\n"); printf ("ical_object_find_in_string:\n"); printf ("-----------------------------------------------------\n"); dump_icalobject (*ico); printf ("-----------------------------------------------------\n"); return CAL_OBJ_FIND_SUCCESS; } } subcomp = icalcomponent_get_next_component (comp, ICAL_ANY_COMPONENT); } printf ("CAL_OBJ_FIND_NOT_FOUND\n"); return CAL_OBJ_FIND_NOT_FOUND; #else /* 1 */ VObject *vcal; VObjectIterator i; CalObjFindStatus status; g_return_val_if_fail (uid != NULL, CAL_OBJ_FIND_SYNTAX_ERROR); g_return_val_if_fail (vcalobj != NULL, CAL_OBJ_FIND_SYNTAX_ERROR); g_return_val_if_fail (ico != NULL, CAL_OBJ_FIND_SYNTAX_ERROR); *ico = NULL; status = CAL_OBJ_FIND_NOT_FOUND; vcal = Parse_MIME (vcalobj, strlen (vcalobj)); if (!vcal) return CAL_OBJ_FIND_SYNTAX_ERROR; initPropIterator (&i, vcal); while (moreIteration (&i)) { VObject *vobj; VObject *uid_prop; char *the_str; vobj = nextVObject (&i); uid_prop = isAPropertyOf (vobj, VCUniqueStringProp); if (!uid_prop) continue; /* str_val() sets the_str to the string representation of the * property. */ str_val (uid_prop); if (strcmp (the_str, uid) == 0) { const char *object_name; object_name = vObjectName (vobj); *ico = ical_object_create_from_vobject (vobj, object_name); if (*ico) status = CAL_OBJ_FIND_SUCCESS; } free (the_str); if (status == CAL_OBJ_FIND_SUCCESS) break; } cleanVObject (vcal); cleanStrTbl (); return status; #endif /* 1 */ } #if 1 /* Creates a VObject with the base information of a calendar */ static VObject * get_calendar_base_vobject (void) { VObject *vobj; time_t now; struct tm tm; /* We call localtime for the side effect of setting tzname */ now = time (NULL); tm = *localtime (&now); vobj = newVObject (VCCalProp); addPropValue (vobj, VCProdIdProp, PRODID); #if defined (HAVE_TM_ZONE) addPropValue (vobj, VCTimeZoneProp, tm.tm_zone); #elif defined (HAVE_TZNAME) addPropValue (vobj, VCTimeZoneProp, tzname[0]); #endif /* Per the vCalendar spec, this must be "1.0" */ addPropValue (vobj, VCVersionProp, "1.0"); return vobj; } #endif /* 0 */ /** * ical_object_to_string: * @ico: A calendar object. * * Converts a vCalendar object to its string representation. It is wrapped * inside a complete VCALENDAR object because other auxiliary information such * as timezones may appear there. * * Return value: String representation of the object. **/ char * ical_object_to_string (iCalObject *ico) { #if 0 icalcomponent *top = icalcomponent_new (ICAL_VCALENDAR_COMPONENT); char *out_cal_string; icalcomponent *comp; printf ("ical_object_to_string:\n"); printf ("-----------------------------------------------------\n"); dump_icalobject (ico); printf ("-----------------------------------------------------\n"); comp = icalcomponent_create_from_ical_object (ico); icalcomponent_add_component (top, comp); out_cal_string = icalcomponent_as_ical_string (top); return g_strdup (out_cal_string); #else /* 1 */ VObject *vcalobj, *vobj; char *buf, *gbuf; vcalobj = get_calendar_base_vobject (); vobj = ical_object_to_vobject (ico); addVObjectProp (vcalobj, vobj); buf = writeMemVObject (NULL, NULL, vcalobj); cleanVObject (vcalobj); cleanStrTbl (); /* We have to g_strdup() it because libversit uses malloc()/realloc(), * and we want clients to be able to use g_free(). Sigh. */ gbuf = g_strdup (buf); free (buf); return gbuf; #endif /* 1 */ } /** * ical_object_compare_dates: * @ico1: A calendar event. * @ico2: A calendar event to compare with @ico1. * * Returns TRUE if the dates of both objects match, including any recurrence * rules. Both calendar objects must have a type of ICAL_EVENT. * * Return value: TRUE if both calendar objects have the same dates. **/ gboolean ical_object_compare_dates (iCalObject *ico1, iCalObject *ico2) { Recurrence *recur1, *recur2; gint num_exdates; GList *elem1, *elem2; time_t *time1, *time2; g_return_val_if_fail (ico1 != NULL, FALSE); g_return_val_if_fail (ico2 != NULL, FALSE); g_return_val_if_fail (ico1->type == ICAL_EVENT, FALSE); g_return_val_if_fail (ico2->type == ICAL_EVENT, FALSE); /* First check the base dates. */ if (ico1->dtstart != ico2->dtstart || ico1->dtend != ico2->dtend) return FALSE; recur1 = ico1->recur; recur2 = ico2->recur; /* If the event doesn't recur, we already know it matches. */ if (!recur1 && !recur2) return TRUE; /* Check that both recur. */ if (!(recur1 && recur2)) return FALSE; /* Now we need to see if the recurrence rules are the same. */ if (recur1->type != recur2->type || recur1->interval != recur2->interval || recur1->enddate != recur2->enddate || recur1->weekday != recur2->weekday || recur1->duration != recur2->duration || recur1->_enddate != recur2->_enddate || recur1->__count != recur2->__count) return FALSE; switch (recur1->type) { case RECUR_MONTHLY_BY_POS: if (recur1->u.month_pos != recur2->u.month_pos) return FALSE; break; case RECUR_MONTHLY_BY_DAY: if (recur1->u.month_day != recur2->u.month_day) return FALSE; break; default: break; } /* Now check if the excluded dates match. */ num_exdates = g_list_length (ico1->exdate); if (g_list_length (ico2->exdate) != num_exdates) return FALSE; if (num_exdates == 0) return TRUE; ico1->exdate = g_list_sort (ico1->exdate, compare_exdates); ico2->exdate = g_list_sort (ico2->exdate, compare_exdates); elem1 = ico1->exdate; elem2 = ico2->exdate; while (elem1) { time1 = (time_t*) elem1->data; time2 = (time_t*) elem2->data; if (*time1 != *time2) return FALSE; elem1 = elem1->next; elem2 = elem2->next; } return TRUE; } static gint compare_exdates (gconstpointer a, gconstpointer b) { const time_t *ca = a, *cb = b; time_t diff = *ca - *cb; return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; } /* Converts any CR/LF sequences in the summary field to spaces so we just have a one-line field. The iCalObjects summary field is changed. */ static void ical_object_normalize_summary (iCalObject *ico) { gchar *src, *dest, ch; gboolean just_output_space = FALSE; src = dest = ico->summary; while ((ch = *src++)) { if (ch == '\n' || ch == '\r') { /* We only output 1 space for each sequence of CR & LF characters. */ if (!just_output_space) { *dest++ = ' '; just_output_space = TRUE; } } else { *dest++ = ch; just_output_space = FALSE; } } *dest = '\0'; } void dump_icalobject (iCalObject *ico) { if (!ico) { printf ("<>\n"); return; } printf ("type "); switch (ico->type) { case ICAL_EVENT: printf ("event"); break; case ICAL_TODO: printf ("todo"); break; case ICAL_JOURNAL: printf ("journal"); break; case ICAL_FBREQUEST: printf ("fbrequest"); break; case ICAL_FBREPLY: printf ("fbreply"); break; case ICAL_BUSYTIME: printf ("busytime"); break; case ICAL_TIMEZONE: printf ("timezone"); break; } printf ("\n"); printf ("attach-length %d\n", g_list_length (ico->attach)); printf ("attendee-length %d\n", g_list_length (ico->attendee)); printf ("catagories-length %d\n", g_list_length (ico->categories)); printf ("class '%s'\n", ico->class ? ico->class : "NULL"); printf ("comment '%s'\n", ico->comment ? ico->comment : "NULL"); printf ("completed %ld=%s", ico->completed, ctime (&ico->completed)); printf ("created %ld=%s", ico->created, ctime (&ico->created)); printf ("contact-length %d\n", g_list_length (ico->contact)); printf ("desc '%s'\n", ico->desc ? ico->desc : "NULL"); printf ("dtstamp %ld=%s", ico->dtstamp, ctime (&ico->dtstamp)); printf ("dtstart %ld=%s", ico->dtstart, ctime (&ico->dtstart)); printf ("dtend %ld=%s", ico->dtend, ctime (&ico->dtend)); printf ("date_only %d\n", ico->date_only); printf ("exdate-length %d\n", g_list_length (ico->exdate)); printf ("exrule-length %d\n", g_list_length (ico->exrule)); printf ("iCalGeo %d %f %f\n", ico->geo.valid, ico->geo.latitude, ico->geo.longitude); printf ("last_mod %ld=%s", ico->last_mod, ctime (&ico->last_mod)); printf ("location '%s'\n", ico->location ? ico->location : "NULL"); printf ("organizer %p\n", ico->organizer); printf ("percent %d\n", ico->percent); printf ("priority %d\n", ico->priority); printf ("rstatus '%s'\n", ico->rstatus ? ico->rstatus : "NULL"); printf ("related-length %d\n", g_list_length (ico->related)); printf ("resources-length %d\n", g_list_length (ico->resources)); printf ("rdate-length %d\n", g_list_length (ico->rdate)); printf ("rrule-length %d\n", g_list_length (ico->rrule)); printf ("seq %d\n", ico->seq); printf ("status '%s'\n", ico->status ? ico->status : "NULL"); printf ("summary '%s'\n", ico->summary ? ico->summary : "NULL"); printf ("transp "); switch (ico->transp) { case ICAL_OPAQUE: printf ("opaque"); break; case ICAL_TRANSPARENT: printf ("transparent"); break; } printf ("\n"); printf ("uid '%s'\n", ico->uid ? ico->uid : "NULL"); printf ("url '%s'\n", ico->url ? ico->url : "NULL"); printf ("recurid %ld=%s", ico->recurid, ctime (&ico->recurid)); printf ("dalarm %d\n", ico->dalarm.enabled); printf ("aalarm %d\n", ico->aalarm.enabled); printf ("palarm %d\n", ico->palarm.enabled); printf ("malarm %d\n", ico->malarm.enabled); printf ("alarms-length %d\n", g_list_length (ico->alarms)); printf ("recur %p\n", ico->recur); printf ("new %d\n", ico->new); printf ("user_data %p\n", ico->user_data); printf ("ref_count %d\n", ico->ref_count); }