diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/calcurse.h | 8 | ||||
-rw-r--r-- | src/ical.c | 1263 | ||||
-rw-r--r-- | src/io.c | 1509 | ||||
-rw-r--r-- | src/pcal.c | 317 |
5 files changed, 1598 insertions, 1501 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 5942b89..f6be9e8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,11 +18,13 @@ calcurse_SOURCES = \ event.c \ getstring.c \ help.c \ + ical.c \ io.c \ keys.c \ llist.c \ note.c \ notify.c \ + pcal.c \ recur.c \ sha1.c \ sigs.c \ diff --git a/src/calcurse.h b/src/calcurse.h index ee6612f..5b4bffe 100644 --- a/src/calcurse.h +++ b/src/calcurse.h @@ -688,6 +688,11 @@ void help_screen (void); enum getstr getstring (WINDOW *, char *, int, int, int); int updatestring (WINDOW *, char **, int, int); +/* ical.c */ +void ical_import_data (FILE *, FILE *, unsigned *, unsigned *, unsigned *, + unsigned *, unsigned *); +void ical_export_data (FILE *); + /* io.c */ unsigned io_fprintln (const char *, const char *, ...); void io_init (char *, char *); @@ -803,6 +808,9 @@ int notify_same_item (long); int notify_same_recur_item (struct recur_apoint *); void notify_config_bar (void); +/* pcal.c */ +void pcal_export_data (FILE *); + /* recur.c */ extern llist_ts_t recur_alist_p; extern llist_t recur_elist; diff --git a/src/ical.c b/src/ical.c new file mode 100644 index 0000000..a40f94a --- /dev/null +++ b/src/ical.c @@ -0,0 +1,1263 @@ +/* + * Calcurse - text-based organizer + * + * Copyright (c) 2004-2011 calcurse Development Team <misc@calcurse.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Send your feedback or comments to : misc@calcurse.org + * Calcurse home page : http://calcurse.org + * + */ + +#include <sys/types.h> + +#include "calcurse.h" +#include "sha1.h" + +#define ICALDATEFMT "%Y%m%d" +#define ICALDATETIMEFMT "%Y%m%dT%H%M%S" + +typedef enum { + ICAL_VEVENT, + ICAL_VTODO, + ICAL_TYPES +} ical_types_e; + +typedef enum { + UNDEFINED, + APPOINTMENT, + EVENT +} ical_vevent_e; + +typedef struct { + enum recur_type type; + int freq; + long until; + unsigned count; +} ical_rpt_t; + +static void ical_export_header (FILE *); +static void ical_export_recur_events (FILE *); +static void ical_export_events (FILE *); +static void ical_export_recur_apoints (FILE *); +static void ical_export_apoints (FILE *); +static void ical_export_todo (FILE *); +static void ical_export_footer (FILE *); + +static char *ical_recur_type[RECUR_TYPES] = + { "", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" }; + +/* iCal alarm notification. */ +static void +ical_export_valarm (FILE *stream) +{ + fputs ("BEGIN:VALARM\n", stream); + pthread_mutex_lock (&nbar.mutex); + fprintf (stream, "TRIGGER:-P%dS\n", nbar.cntdwn); + pthread_mutex_unlock (&nbar.mutex); + fputs ("ACTION:DISPLAY\n", stream); + fputs ("END:VALARM\n", stream); +} + +/* Export header. */ +static void +ical_export_header (FILE *stream) +{ + fputs ("BEGIN:VCALENDAR\n", stream); + fprintf (stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION); + fputs ("VERSION:2.0\n", stream); +} + +/* Export footer. */ +static void +ical_export_footer (FILE *stream) +{ + fputs ("END:VCALENDAR\n", stream); +} + +/* Export recurrent events. */ +static void +ical_export_recur_events (FILE *stream) +{ + llist_item_t *i, *j; + char ical_date[BUFSIZ]; + + LLIST_FOREACH (&recur_elist, i) + { + struct recur_event *rev = LLIST_GET_DATA (i); + date_sec2date_fmt (rev->day, ICALDATEFMT, ical_date); + fputs ("BEGIN:VEVENT\n", stream); + fprintf (stream, "DTSTART:%s\n", ical_date); + fprintf (stream, "RRULE:FREQ=%s;INTERVAL=%d", + ical_recur_type[rev->rpt->type], rev->rpt->freq); + + if (rev->rpt->until != 0) + { + date_sec2date_fmt (rev->rpt->until, ICALDATEFMT, ical_date); + fprintf (stream, ";UNTIL=%s\n", ical_date); + } + else + fputc ('\n', stream); + + if (LLIST_FIRST (&rev->exc)) + { + fputs ("EXDATE:", stream); + LLIST_FOREACH (&rev->exc, j) + { + struct excp *exc = LLIST_GET_DATA (j); + date_sec2date_fmt (exc->st, ICALDATEFMT, ical_date); + fprintf (stream, "%s", ical_date); + if (LLIST_NEXT (j)) + fputc (',', stream); + else + fputc ('\n', stream); + } + } + + fprintf (stream, "SUMMARY:%s\n", rev->mesg); + fputs ("END:VEVENT\n", stream); + } +} + +/* Export events. */ +static void +ical_export_events (FILE *stream) +{ + llist_item_t *i; + char ical_date[BUFSIZ]; + + LLIST_FOREACH (&eventlist, i) + { + struct event *ev = LLIST_TS_GET_DATA (i); + date_sec2date_fmt (ev->day, ICALDATEFMT, ical_date); + fputs ("BEGIN:VEVENT\n", stream); + fprintf (stream, "DTSTART:%s\n", ical_date); + fprintf (stream, "SUMMARY:%s\n", ev->mesg); + fputs ("END:VEVENT\n", stream); + } +} + +/* Export recurrent appointments. */ +static void +ical_export_recur_apoints (FILE *stream) +{ + llist_item_t *i, *j; + char ical_datetime[BUFSIZ]; + char ical_date[BUFSIZ]; + + LLIST_TS_LOCK (&recur_alist_p); + LLIST_TS_FOREACH (&recur_alist_p, i) + { + struct recur_apoint *rapt = LLIST_TS_GET_DATA (i); + + date_sec2date_fmt (rapt->start, ICALDATETIMEFMT, ical_datetime); + fputs ("BEGIN:VEVENT\n", stream); + fprintf (stream, "DTSTART:%s\n", ical_datetime); + fprintf (stream, "DURATION:PT0H0M%ldS\n", rapt->dur); + fprintf (stream, "RRULE:FREQ=%s;INTERVAL=%d", + ical_recur_type[rapt->rpt->type], rapt->rpt->freq); + + if (rapt->rpt->until != 0) + { + date_sec2date_fmt (rapt->rpt->until + HOURINSEC, ICALDATEFMT, + ical_date); + fprintf (stream, ";UNTIL=%s\n", ical_date); + } + else + fputc ('\n', stream); + + if (LLIST_FIRST (&rapt->exc)) + { + fputs ("EXDATE:", stream); + LLIST_FOREACH (&rapt->exc, j) + { + struct excp *exc = LLIST_GET_DATA (j); + date_sec2date_fmt (exc->st, ICALDATEFMT, ical_date); + fprintf (stream, "%s", ical_date); + if (LLIST_NEXT (j)) + fputc (',', stream); + else + fputc ('\n', stream); + } + } + + fprintf (stream, "SUMMARY:%s\n", rapt->mesg); + if (rapt->state & APOINT_NOTIFY) + ical_export_valarm (stream); + fputs ("END:VEVENT\n", stream); + } + LLIST_TS_UNLOCK (&recur_alist_p); +} + +/* Export appointments. */ +static void +ical_export_apoints (FILE *stream) +{ + llist_item_t *i; + char ical_datetime[BUFSIZ]; + + LLIST_TS_LOCK (&alist_p); + LLIST_TS_FOREACH (&alist_p, i) + { + struct apoint *apt = LLIST_TS_GET_DATA (i); + date_sec2date_fmt (apt->start, ICALDATETIMEFMT, ical_datetime); + fputs ("BEGIN:VEVENT\n", stream); + fprintf (stream, "DTSTART:%s\n", ical_datetime); + fprintf (stream, "DURATION:P%ldDT%ldH%ldM%ldS\n", + apt->dur / DAYINSEC, + (apt->dur / HOURINSEC) % DAYINHOURS, + (apt->dur / MININSEC) % HOURINMIN, + apt->dur % MININSEC); + fprintf (stream, "SUMMARY:%s\n", apt->mesg); + if (apt->state & APOINT_NOTIFY) + ical_export_valarm (stream); + fputs ("END:VEVENT\n", stream); + } + LLIST_TS_UNLOCK (&alist_p); +} + +/* Export todo items. */ +static void +ical_export_todo (FILE *stream) +{ + llist_item_t *i; + + LLIST_FOREACH (&todolist, i) + { + struct todo *todo = LLIST_TS_GET_DATA (i); + if (todo->id < 0) /* completed items */ + continue; + + fputs ("BEGIN:VTODO\n", stream); + fprintf (stream, "PRIORITY:%d\n", todo->id); + fprintf (stream, "SUMMARY:%s\n", todo->mesg); + fputs ("END:VTODO\n", stream); + } +} + +/* Print a header to describe import log report format. */ +static void +ical_log_init (FILE *log, float version) +{ + const char *header = + "+-------------------------------------------------------------------+\n" + "| Calcurse icalendar import log. |\n" + "| |\n" + "| Items imported from icalendar file, version %1.1f |\n" + "| Some items could not be imported, they are described hereafter. |\n" + "| The log line format is as follows: |\n" + "| |\n" + "| TYPE [LINE]: DESCRIPTION |\n" + "| |\n" + "| where: |\n" + "| * TYPE represents the item type ('VEVENT' or 'VTODO') |\n" + "| * LINE is the line in the input stream at which this item begins |\n" + "| * DESCRIPTION indicates why the item could not be imported |\n" + "+-------------------------------------------------------------------+\n\n"; + + if (log) + fprintf (log, header, version); +} + +/* + * Used to build a report of the import process. + * The icalendar item for which a problem occurs is mentioned (by giving its + * first line inside the icalendar file), together with a message describing the + * problem. + */ +static void +ical_log (FILE *log, ical_types_e type, unsigned lineno, char *msg) +{ + const char *typestr[ICAL_TYPES] = {"VEVENT", "VTODO"}; + + RETURN_IF (type < 0 || type >= ICAL_TYPES, _("unknown ical type")); + if (log) + fprintf (log, "%s [%d]: %s\n", typestr[type], lineno, msg); +} + +static void +ical_store_todo (int priority, char *mesg, char *note) +{ + todo_add (mesg, priority, note); + mem_free (mesg); + erase_note (¬e); +} + +static void +ical_store_event (char *mesg, char *note, long day, long end, ical_rpt_t *rpt, + llist_t *exc) +{ + const int EVENTID = 1; + + if (rpt) + { + recur_event_new (mesg, note, day, EVENTID, rpt->type, rpt->freq, + rpt->until, exc); + mem_free (rpt); + } + else if (end && end != day) + { + /* Here we have an event that spans over several days. */ + rpt = mem_malloc (sizeof (ical_rpt_t)); + rpt->type = RECUR_DAILY; + rpt->freq = 1; + rpt->count = 0; + rpt->until = end; + recur_event_new (mesg, note, day, EVENTID, rpt->type, rpt->freq, + rpt->until, exc); + mem_free (rpt); + } + else + { + event_new (mesg, note, day, EVENTID); + } + mem_free (mesg); + erase_note (¬e); +} + +static void +ical_store_apoint (char *mesg, char *note, long start, long dur, + ical_rpt_t *rpt, llist_t *exc, int has_alarm) +{ + char state = 0L; + + if (has_alarm) + state |= APOINT_NOTIFY; + if (rpt) + { + recur_apoint_new (mesg, note, start, dur, state, rpt->type, rpt->freq, + rpt->until, exc); + mem_free (rpt); + } + else + { + apoint_new (mesg, note, start, dur, state); + } + mem_free (mesg); + erase_note (¬e); +} + +/* + * Returns an allocated string representing the string given in argument once + * unformatted. + * + * Note: + * Even if the RFC2445 recommends not to have more than 75 octets on one line of + * text, I prefer not to restrict the parsing to this size, thus I use a buffer + * of size BUFSIZ. + * + * Extract from RFC2445: + * Lines of text SHOULD NOT be longer than 75 octets, excluding the line + * break. + */ +static char * +ical_unformat_line (char *line) +{ + char *p, uline[BUFSIZ]; + int len; + + if (strlen (line) >= BUFSIZ) + return NULL; + + bzero (uline, BUFSIZ); + for (len = 0, p = line; *p; p++) + { + switch (*p) + { + case '\\': + switch (*(p + 1)) + { + case 'n': + uline[len++] = '\n'; + p++; + break; + case 't': + uline[len++] = '\t'; + p++; + break; + case ';': + case ':': + case ',': + uline[len++] = *(p + 1); + p++; + break; + default: + uline[len++] = *p; + break; + } + break; + default: + uline[len++] = *p; + break; + } + } + + return mem_strdup (uline); +} + +static void +ical_readline_init (FILE *fdi, char *buf, char *lstore, unsigned *ln) +{ + char *eol; + + *buf = *lstore = '\0'; + fgets (lstore, BUFSIZ, fdi); + if ((eol = strchr(lstore, '\n')) != NULL) + *eol = '\0'; + (*ln)++; +} + +static int +ical_readline (FILE *fdi, char *buf, char *lstore, unsigned *ln) +{ + char *eol; + + strncpy (buf, lstore, BUFSIZ); + (*ln)++; + + while (fgets (lstore, BUFSIZ, fdi) != NULL) + { + if ((eol = strchr(lstore, '\n')) != NULL) + *eol = '\0'; + if (*lstore != SPACE && *lstore != TAB) + break; + strncat (buf, lstore + 1, BUFSIZ); + buf[BUFSIZ - 1] = '\0'; + (*ln)++; + } + + if (feof (fdi)) + { + *lstore = '\0'; + if (*buf == '\0') + return 0; + } + + return 1; +} + +static float +ical_chk_header (FILE *fd, char *buf, char *lstore, unsigned *lineno) +{ + const int HEADER_MALFORMED = -1; + const struct string icalheader = STRING_BUILD ("BEGIN:VCALENDAR"); + float version; + + if (!ical_readline (fd, buf, lstore, lineno)) + return HEADER_MALFORMED; + + str_toupper (buf); + if (strncmp (buf, icalheader.str, icalheader.len) != 0) + return HEADER_MALFORMED; + + while (!sscanf (buf, "VERSION:%f", &version)) + { + if (!ical_readline (fd, buf, lstore, lineno)) + return HEADER_MALFORMED; + } + return version; +} + +/* + * iCalendar date-time format is based on the ISO 8601 complete + * representation. It should be something like : DATE 'T' TIME + * where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'. + * The time and 'T' separator are optional (in the case of an day-long event). + * + * Optionnaly, if the type pointer is given, specify if it is an event + * (no time is given, meaning it is an all-day event), or an appointment + * (time is given). + * + * The timezone is not yet handled by calcurse. + */ +static long +ical_datetime2long (char *datestr, ical_vevent_e *type) +{ + const int NOTFOUND = 0, FORMAT_DATE = 3, FORMAT_DATETIME = 5; + struct date date; + unsigned hour, min; + long datelong; + int format; + + format = sscanf (datestr, "%04u%02u%02uT%02u%02u", + &date.yyyy, &date.mm, &date.dd, &hour, &min); + if (format == FORMAT_DATE) + { + if (type) + *type = EVENT; + datelong = date2sec (date, 0, 0); + } + else if (format == FORMAT_DATETIME) + { + if (type) + *type = APPOINTMENT; + datelong = date2sec (date, hour, min); + } + else + { + datelong = NOTFOUND; + } + return datelong; +} + +static long +ical_durtime2long (char *timestr) +{ + long timelong; + char *p; + + if ((p = strchr (timestr, 'T')) == NULL) + timelong = 0; + else + { + int nbmatch; + struct { + unsigned hour, min, sec; + } time; + + p++; + bzero (&time, sizeof time); + nbmatch = sscanf (p, "%uH%uM%uS", &time.hour, &time.min, &time.sec); + if (nbmatch < 1 || nbmatch > 3) + timelong = 0; + else + timelong = time.hour * HOURINSEC + time.min * MININSEC + time.sec; + } + return timelong; +} + +/* + * Extract from RFC2445: + * + * Value Name: DURATION + * + * Purpose: This value type is used to identify properties that contain + * duration of time. + * + * Formal Definition: The value type is defined by the following + * notation: + * + * dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week) + * dur-date = dur-day [dur-time] + * dur-time = "T" (dur-hour / dur-minute / dur-second) + * dur-week = 1*DIGIT "W" + * dur-hour = 1*DIGIT "H" [dur-minute] + * dur-minute = 1*DIGIT "M" [dur-second] + * dur-second = 1*DIGIT "S" + * dur-day = 1*DIGIT "D" + * + * Example: A duration of 15 days, 5 hours and 20 seconds would be: + * P15DT5H0M20S + * A duration of 7 weeks would be: + * P7W + */ +static long +ical_dur2long (char *durstr) +{ + const int NOTFOUND = -1; + long durlong; + char *p; + struct { + unsigned week, day; + } date; + + bzero (&date, sizeof date); + if ((p = strchr (durstr, 'P')) == NULL) + durlong = NOTFOUND; + else + { + p++; + if (*p == '-') + return NOTFOUND; + else if (*p == '+') + p++; + + if (*p == 'T') /* dur-time */ + durlong = ical_durtime2long (p); + else if (strchr (p, 'W')) /* dur-week */ + { + if (sscanf (p, "%u", &date.week) == 1) + durlong = date.week * WEEKINDAYS * DAYINSEC; + else + durlong = NOTFOUND; + } + else + { + if (strchr (p, 'D')) /* dur-date */ + { + if (sscanf (p, "%uD", &date.day) == 1) + { + durlong = date.day * DAYINSEC; + durlong += ical_durtime2long (p); + } + else + durlong = NOTFOUND; + } + else + durlong = NOTFOUND; + } + } + return durlong; +} + +/* + * Compute the vevent repetition end date from the repetition count. + * + * Extract from RFC2445: + * The COUNT rule part defines the number of occurrences at which to + * range-bound the recurrence. The "DTSTART" property value, if specified, + * counts as the first occurrence. + */ +static long +ical_compute_rpt_until (long start, ical_rpt_t *rpt) +{ + long until; + + switch (rpt->type) + { + case RECUR_DAILY: + until = date_sec_change (start, 0, rpt->freq * (rpt->count - 1)); + break; + case RECUR_WEEKLY: + until = date_sec_change (start, 0, + rpt->freq * WEEKINDAYS * (rpt->count - 1)); + break; + case RECUR_MONTHLY: + until = date_sec_change (start, rpt->freq * (rpt->count - 1), 0); + break; + case RECUR_YEARLY: + until = date_sec_change (start, rpt->freq * 12 * (rpt->count - 1), 0); + break; + default: + until = 0; + break; + /* NOTREACHED */ + } + return until; +} + +/* + * Read a recurrence rule from an iCalendar RRULE string. + * + * Value Name: RECUR + * + * Purpose: This value type is used to identify properties that contain + * a recurrence rule specification. + * + * Formal Definition: The value type is defined by the following + * notation: + * + * recur = "FREQ"=freq *( + * + * ; either UNTIL or COUNT may appear in a 'recur', + * ; but UNTIL and COUNT MUST NOT occur in the same 'recur' + * + * ( ";" "UNTIL" "=" enddate ) / + * ( ";" "COUNT" "=" 1*DIGIT ) / + * + * ; the rest of these keywords are optional, + * ; but MUST NOT occur more than + * ; once + * + * ( ";" "INTERVAL" "=" 1*DIGIT ) / + * ( ";" "BYSECOND" "=" byseclist ) / + * ( ";" "BYMINUTE" "=" byminlist ) / + * ( ";" "BYHOUR" "=" byhrlist ) / + * ( ";" "BYDAY" "=" bywdaylist ) / + * ( ";" "BYMONTHDAY" "=" bymodaylist ) / + * ( ";" "BYYEARDAY" "=" byyrdaylist ) / + * ( ";" "BYWEEKNO" "=" bywknolist ) / + * ( ";" "BYMONTH" "=" bymolist ) / + * ( ";" "BYSETPOS" "=" bysplist ) / + * ( ";" "WKST" "=" weekday ) / + * ( ";" x-name "=" text ) + * ) +*/ +static ical_rpt_t * +ical_read_rrule (FILE *log, char *rrulestr, unsigned *noskipped, + const int itemline) +{ + const struct string daily = STRING_BUILD ("DAILY"); + const struct string weekly = STRING_BUILD ("WEEKLY"); + const struct string monthly = STRING_BUILD ("MONTHLY"); + const struct string yearly = STRING_BUILD ("YEARLY"); + const struct string count = STRING_BUILD ("COUNT="); + const struct string interv = STRING_BUILD ("INTERVAL="); + unsigned interval; + ical_rpt_t *rpt; + char *p; + + rpt = NULL; + if ((p = strchr (rrulestr, ':')) != NULL) + { + char freqstr[BUFSIZ]; + + p++; + rpt = mem_malloc (sizeof (ical_rpt_t)); + bzero (rpt, sizeof (ical_rpt_t)); + if (sscanf (p, "FREQ=%s", freqstr) != 1) + { + ical_log (log, ICAL_VEVENT, itemline, + _("recurrence frequence not found.")); + (*noskipped)++; + mem_free (rpt); + return NULL; + } + else + { + if (strncmp (freqstr, daily.str, daily.len) == 0) + rpt->type = RECUR_DAILY; + else if (strncmp (freqstr, weekly.str, weekly.len) == 0) + rpt->type = RECUR_WEEKLY; + else if (strncmp (freqstr, monthly.str, monthly.len) == 0) + rpt->type = RECUR_MONTHLY; + else if (strncmp (freqstr, yearly.str, yearly.len) == 0) + rpt->type = RECUR_YEARLY; + else + { + ical_log (log, ICAL_VEVENT, itemline, + _("recurrence frequence not recognized.")); + (*noskipped)++; + mem_free (rpt); + return NULL; + } + } + /* + The UNTIL rule part defines a date-time value which bounds the + recurrence rule in an inclusive manner. If not present, and the + COUNT rule part is also not present, the RRULE is considered to + repeat forever. + + The COUNT rule part defines the number of occurrences at which to + range-bound the recurrence. The "DTSTART" property value, if + specified, counts as the first occurrence. + */ + if ((p = strstr (rrulestr, "UNTIL")) != NULL) + { + char *untilstr; + + untilstr = strchr (p, '='); + rpt->until = ical_datetime2long (++untilstr, NULL); + } + else + { + unsigned cnt; + char *countstr; + + if ((countstr = strstr (rrulestr, count.str)) != NULL) + { + countstr += count.len; + if (sscanf (countstr, "%u", &cnt) != 1) + { + rpt->until = 0; + /* endless repetition */ + } + else + { + rpt->count = cnt; + } + } + else + rpt->until = 0; + } + + if ((p = strstr (rrulestr, interv.str)) != NULL) + { + p += interv.len; + if (sscanf (p, "%u", &interval) != 1) + { + rpt->freq = 1; + /* default frequence if none specified */ + } + else + { + rpt->freq = interval; + } + } + else + { + rpt->freq = 1; + } + } + else + { + ical_log (log, ICAL_VEVENT, itemline, _("recurrence rule malformed.")); + (*noskipped)++; + } + return rpt; +} + +static void +ical_add_exc (llist_t *exc_head, long date) +{ + if (date != 0) + { + struct excp *exc = mem_malloc (sizeof (struct excp)); + exc->st = date; + + LLIST_ADD (exc_head, exc); + } +} + +/* + * This property defines the list of date/time exceptions for a + * recurring calendar component. + */ +void +ical_read_exdate (llist_t *exc, FILE *log, char *exstr, unsigned *noskipped, + const int itemline) +{ + char *p, *q; + long date; + + LLIST_INIT (exc); + if ((p = strchr (exstr, ':')) != NULL) + { + p++; + while ((q = strchr (p, ',')) != NULL) + { + char buf[BUFSIZ]; + const int buflen = q - p; + + strncpy (buf, p, buflen); + buf[buflen] = '\0'; + date = ical_datetime2long (buf, NULL); + ical_add_exc (exc, date); + p = ++q; + } + date = ical_datetime2long (p, NULL); + ical_add_exc (exc, date); + } + else + { + ical_log (log, ICAL_VEVENT, itemline, + _("recurrence exception dates malformed.")); + (*noskipped)++; + } +} + +/* Return an allocated string containing the name of the newly created note. */ +static char * +ical_read_note (char *line, unsigned *noskipped, ical_vevent_e item_type, + const int itemline, FILE *log) +{ + char *sha1 = mem_malloc (SHA1_DIGESTLEN * 2 + 1); + char *p, *notestr, fullnotename[BUFSIZ]; + FILE *fdo; + + if ((p = strchr (line, ':')) != NULL) + { + p++; + notestr = ical_unformat_line (p); + if (notestr == NULL) + { + ical_log (log, item_type, itemline, + _("could not get entire item description.")); + (*noskipped)++; + return NULL; + } + else if (strlen (notestr) == 0) + { + mem_free (notestr); + return NULL; + } + else + { + sha1_digest (notestr, sha1); + snprintf (fullnotename, BUFSIZ, "%s%s", path_notes, sha1); + fdo = fopen (fullnotename, "w"); + EXIT_IF (fdo == NULL, _("Warning: could not open %s, Aborting..."), + fullnotename); + fprintf (fdo, "%s", notestr); + file_close (fdo, __FILE_POS__); + mem_free (notestr); + return sha1; + } + } + else + { + ical_log (log, item_type, itemline, _("description malformed.")); + (*noskipped)++; + return NULL; + } +} + +/* Returns an allocated string containing the ical item summary. */ +static char * +ical_read_summary (char *line) +{ + char *p, *summary; + + if ((p = strchr (line, ':')) != NULL) + { + p++; + summary = ical_unformat_line (p); + return summary; + } + else + return NULL; +} + +static void +ical_read_event (FILE *fdi, FILE *log, unsigned *noevents, unsigned *noapoints, + unsigned *noskipped, char *buf, char *lstore, + unsigned *lineno) +{ + const int ITEMLINE = *lineno; + const struct string endevent = STRING_BUILD ("END:VEVENT"); + const struct string summary = STRING_BUILD ("SUMMARY"); + const struct string dtstart = STRING_BUILD ("DTSTART"); + const struct string dtend = STRING_BUILD ("DTEND"); + const struct string duration = STRING_BUILD ("DURATION"); + const struct string rrule = STRING_BUILD ("RRULE"); + const struct string exdate = STRING_BUILD ("EXDATE"); + const struct string alarm = STRING_BUILD ("BEGIN:VALARM"); + const struct string endalarm = STRING_BUILD ("END:VALARM"); + const struct string desc = STRING_BUILD ("DESCRIPTION"); + ical_vevent_e vevent_type; + char *p, buf_upper[BUFSIZ]; + struct { + llist_t exc; + ical_rpt_t *rpt; + char *mesg, *note; + long start, end, dur; + int has_alarm; + } vevent; + int skip_alarm; + + vevent_type = UNDEFINED; + bzero (&vevent, sizeof vevent); + skip_alarm = 0; + while (ical_readline (fdi, buf, lstore, lineno)) + { + strncpy (buf_upper, buf, BUFSIZ); + buf_upper[BUFSIZ - 1] = '\0'; + str_toupper (buf_upper); + + if (skip_alarm) + { + /* Need to skip VALARM properties because some keywords could + interfere, such as DURATION, SUMMARY,.. */ + if (strncmp (buf_upper, endalarm.str, endalarm.len) == 0) + skip_alarm = 0; + continue; + } + if (strncmp (buf_upper, endevent.str, endevent.len) == 0) + { + if (vevent.mesg) + { + if (vevent.rpt && vevent.rpt->count) + vevent.rpt->until = ical_compute_rpt_until (vevent.start, + vevent.rpt); + + switch (vevent_type) + { + case APPOINTMENT: + if (vevent.start == 0) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("appointment has no start time.")); + goto cleanup; + } + if (vevent.dur == 0) + { + if (vevent.end == 0) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("could not compute duration " + "(no end time).")); + goto cleanup; + } + else if (vevent.start == vevent.end) + { + vevent_type = EVENT; + vevent.end = 0L; + ical_store_event (vevent.mesg, vevent.note, + vevent.start, vevent.end, + vevent.rpt, &vevent.exc); + (*noevents)++; + return; + } + else + { + vevent.dur = vevent.end - vevent.start; + if (vevent.dur < 0) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("item has a negative duration.")); + goto cleanup; + } + } + } + ical_store_apoint (vevent.mesg, vevent.note, vevent.start, + vevent.dur, vevent.rpt, &vevent.exc, + vevent.has_alarm); + (*noapoints)++; + break; + case EVENT: + if (vevent.start == 0) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("event date is not defined.")); + goto cleanup; + } + ical_store_event (vevent.mesg, vevent.note, vevent.start, + vevent.end, vevent.rpt, &vevent.exc); + (*noevents)++; + break; + case UNDEFINED: + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("item could not be identified.")); + goto cleanup; + break; + } + } + else + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("could not retrieve item summary.")); + goto cleanup; + } + return; + } + else + { + if (strncmp (buf_upper, dtstart.str, dtstart.len) == 0) + { + if ((p = strchr (buf, ':')) != NULL) + vevent.start = ical_datetime2long (++p, &vevent_type); + if (!vevent.start) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("could not retrieve event start time.")); + goto cleanup; + } + } + else if (strncmp (buf_upper, dtend.str, dtend.len) == 0) + { + if ((p = strchr (buf, ':')) != NULL) + vevent.end = ical_datetime2long (++p, &vevent_type); + if (!vevent.end) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("could not retrieve event end time.")); + goto cleanup; + } + } + else if (strncmp (buf_upper, duration.str, duration.len) == 0) + { + if ((vevent.dur = ical_dur2long (buf)) <= 0) + { + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("item duration malformed.")); + goto cleanup; + } + } + else if (strncmp (buf_upper, rrule.str, rrule.len) == 0) + { + vevent.rpt = ical_read_rrule (log, buf, noskipped, ITEMLINE); + } + else if (strncmp (buf_upper, exdate.str, exdate.len) == 0) + { + ical_read_exdate (&vevent.exc, log, buf, noskipped, ITEMLINE); + } + else if (strncmp (buf_upper, summary.str, summary.len) == 0) + { + vevent.mesg = ical_read_summary (buf); + } + else if (strncmp (buf_upper, alarm.str, alarm.len) == 0) + { + skip_alarm = 1; + vevent.has_alarm = 1; + } + else if (strncmp (buf_upper, desc.str, desc.len) == 0) + { + vevent.note = ical_read_note (buf, noskipped, ICAL_VEVENT, + ITEMLINE, log); + } + } + } + ical_log (log, ICAL_VEVENT, ITEMLINE, + _("The ical file seems to be malformed. " + "The end of item was not found.")); + +cleanup: + + if (vevent.note) + mem_free (vevent.note); + if (vevent.mesg) + mem_free (vevent.mesg); + if (vevent.rpt) + mem_free (vevent.rpt); + LLIST_FREE (&vevent.exc); + (*noskipped)++; +} + +static void +ical_read_todo (FILE *fdi, FILE *log, unsigned *notodos, unsigned *noskipped, + char *buf, char *lstore, unsigned *lineno) +{ + const struct string endtodo = STRING_BUILD ("END:VTODO"); + const struct string summary = STRING_BUILD ("SUMMARY"); + const struct string alarm = STRING_BUILD ("BEGIN:VALARM"); + const struct string endalarm = STRING_BUILD ("END:VALARM"); + const struct string desc = STRING_BUILD ("DESCRIPTION"); + const int LOWEST = 9; + const int ITEMLINE = *lineno; + char buf_upper[BUFSIZ]; + struct { + char *mesg, *note; + int has_priority, priority; + } vtodo; + int skip_alarm; + + bzero (&vtodo, sizeof vtodo); + skip_alarm = 0; + while (ical_readline (fdi, buf, lstore, lineno)) + { + strncpy (buf_upper, buf, BUFSIZ); + buf_upper[BUFSIZ - 1] = '\0'; + str_toupper (buf_upper); + if (skip_alarm) + { + /* Need to skip VALARM properties because some keywords could + interfere, such as DURATION, SUMMARY,.. */ + if (strncmp (buf_upper, endalarm.str, endalarm.len) == 0) + skip_alarm = 0; + continue; + } + if (strncmp (buf_upper, endtodo.str, endtodo.len) == 0) + { + if (!vtodo.has_priority) + vtodo.priority = LOWEST; + if (vtodo.mesg) + { + ical_store_todo (vtodo.priority, vtodo.mesg, vtodo.note); + (*notodos)++; + } + else + { + ical_log (log, ICAL_VTODO, ITEMLINE, + _("could not retrieve item summary.")); + goto cleanup; + } + return; + } + else + { + int tmpint; + + if (sscanf (buf_upper, "PRIORITY:%d", &tmpint) == 1) + { + if (tmpint <= 9 && tmpint >= 1) + { + vtodo.priority = tmpint; + vtodo.has_priority = 1; + } + else + { + ical_log (log, ICAL_VTODO, ITEMLINE, + _("item priority is not acceptable " + "(must be between 1 and 9).")); + vtodo.priority = LOWEST; + } + } + else if (strncmp (buf_upper, summary.str, summary.len) == 0) + { + vtodo.mesg = ical_read_summary (buf); + } + else if (strncmp (buf_upper, alarm.str, alarm.len) == 0) + { + skip_alarm = 1; + } + else if (strncmp (buf_upper, desc.str, desc.len) == 0) + { + vtodo.note = ical_read_note (buf, noskipped, ICAL_VTODO, + ITEMLINE, log); + } + } + } + ical_log (log, ICAL_VTODO, ITEMLINE, + _("The ical file seems to be malformed. " + "The end of item was not found.")); + +cleanup: + + if (vtodo.note) + mem_free (vtodo.note); + if (vtodo.mesg) + mem_free (vtodo.mesg); + (*noskipped)++; +} + +/* Import calcurse data. */ +void +ical_import_data (FILE *stream, FILE *log, unsigned *events, unsigned *apoints, + unsigned *todos, unsigned *lines, unsigned *skipped) +{ + const struct string vevent = STRING_BUILD ("BEGIN:VEVENT"); + const struct string vtodo = STRING_BUILD ("BEGIN:VTODO"); + char buf[BUFSIZ], lstore[BUFSIZ]; + float ical_version; + + ical_readline_init (stream, buf, lstore, lines); + ical_version = ical_chk_header (stream, buf, lstore, lines); + RETURN_IF (ical_version < 0, + _("Warning: ical header malformed or wrong version number. " + "Aborting...")); + + ical_log_init (log, ical_version); + + while (ical_readline (stream, buf, lstore, lines)) + { + (*lines)++; + str_toupper (buf); + if (strncmp (buf, vevent.str, vevent.len) == 0) + { + ical_read_event (stream, log, events, apoints, skipped, buf, lstore, + lines); + } + else if (strncmp (buf, vtodo.str, vtodo.len) == 0) + { + ical_read_todo (stream, log, todos, skipped, buf, lstore, lines); + } + } +} + +/* Export calcurse data. */ +void +ical_export_data (FILE *stream) +{ + ical_export_header (stream); + ical_export_recur_events (stream); + ical_export_events (stream); + ical_export_recur_apoints (stream); + ical_export_apoints (stream); + ical_export_todo (stream); + ical_export_footer (stream); +} @@ -47,9 +47,6 @@ #include "calcurse.h" #include "sha1.h" -#define ICALDATEFMT "%Y%m%d" -#define ICALDATETIMEFMT "%Y%m%dT%H%M%S" - typedef enum { PROGRESS_BAR_SAVE, PROGRESS_BAR_LOAD, @@ -69,70 +66,12 @@ enum { PROGRESS_BAR_EXPORT_TODO }; -typedef enum { - ICAL_VEVENT, - ICAL_VTODO, - ICAL_TYPES -} ical_types_e; - -typedef enum { - UNDEFINED, - APPOINTMENT, - EVENT -} ical_vevent_e; - -typedef struct { - enum recur_type type; - int freq; - long until; - unsigned count; -} ical_rpt_t; - struct ht_keybindings_s { char *label; enum key key; HTABLE_ENTRY (ht_keybindings_s); }; -/* Type definition for callbacks to multiple-mode export functions. */ -typedef void (*cb_export_t)(FILE *); -typedef void (*cb_dump_t)(FILE *, long, long, char *); - -/* Static functions used to add export functionalities. */ -static void ical_export_header (FILE *); -static void ical_export_recur_events (FILE *); -static void ical_export_events (FILE *); -static void ical_export_recur_apoints (FILE *); -static void ical_export_apoints (FILE *); -static void ical_export_todo (FILE *); -static void ical_export_footer (FILE *); - -static void pcal_export_header (FILE *); -static void pcal_export_recur_events (FILE *); -static void pcal_export_events (FILE *); -static void pcal_export_recur_apoints (FILE *); -static void pcal_export_apoints (FILE *); -static void pcal_export_todo (FILE *); -static void pcal_export_footer (FILE *); - -cb_export_t cb_export_header[IO_EXPORT_NBTYPES] = - {ical_export_header, pcal_export_header}; -cb_export_t cb_export_recur_events[IO_EXPORT_NBTYPES] = - {ical_export_recur_events, pcal_export_recur_events}; -cb_export_t cb_export_events[IO_EXPORT_NBTYPES] = - {ical_export_events, pcal_export_events}; -cb_export_t cb_export_recur_apoints[IO_EXPORT_NBTYPES] = - {ical_export_recur_apoints, pcal_export_recur_apoints}; -cb_export_t cb_export_apoints[IO_EXPORT_NBTYPES] = - {ical_export_apoints, pcal_export_apoints}; -cb_export_t cb_export_todo[IO_EXPORT_NBTYPES] = - {ical_export_todo, pcal_export_todo}; -cb_export_t cb_export_footer[IO_EXPORT_NBTYPES] = - {ical_export_footer, pcal_export_footer}; - -static char *ical_recur_type[RECUR_TYPES] = - { "", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" }; - /* Draw a progress bar while saving, loading or exporting data. */ static void progress_bar (progress_bar_t type, int progress) @@ -241,446 +180,6 @@ get_export_stream (enum export_type type) return stream; } -/* - * Travel through each occurence of an item, and execute the given callback - * (mainly used to export data). - */ -static void -foreach_date_dump (const long date_end, struct rpt *rpt, llist_t *exc, - long item_first_date, long item_dur, char *item_mesg, - cb_dump_t cb_dump, FILE *stream) -{ - long date, item_time; - struct tm lt; - time_t t; - - t = item_first_date; - lt = *localtime (&t); - lt.tm_hour = lt.tm_min = lt.tm_sec = 0; - lt.tm_isdst = -1; - date = mktime (<); - item_time = item_first_date - date; - - while (date <= date_end && date <= rpt->until) - { - if (recur_item_inday (item_first_date, item_dur, exc, rpt->type, - rpt->freq, rpt->until, date)) - { - (*cb_dump)(stream, date + item_time, item_dur, item_mesg); - } - switch (rpt->type) - { - case RECUR_DAILY: - date = date_sec_change (date, 0, rpt->freq); - break; - case RECUR_WEEKLY: - date = date_sec_change (date, 0, rpt->freq * WEEKINDAYS); - break; - case RECUR_MONTHLY: - date = date_sec_change (date, rpt->freq, 0); - break; - case RECUR_YEARLY: - date = date_sec_change (date, rpt->freq * 12, 0); - break; - default: - EXIT (_("incoherent repetition type")); - /* NOTREACHED */ - break; - } - } -} - -/* iCal alarm notification. */ -static void -ical_export_valarm (FILE *stream) -{ - fputs ("BEGIN:VALARM\n", stream); - pthread_mutex_lock (&nbar.mutex); - fprintf (stream, "TRIGGER:-P%dS\n", nbar.cntdwn); - pthread_mutex_unlock (&nbar.mutex); - fputs ("ACTION:DISPLAY\n", stream); - fputs ("END:VALARM\n", stream); -} - -/* Export header. */ -static void -ical_export_header (FILE *stream) -{ - fputs ("BEGIN:VCALENDAR\n", stream); - fprintf (stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION); - fputs ("VERSION:2.0\n", stream); -} - -static void -pcal_export_header (FILE *stream) -{ - fputs ("# calcurse pcal export\n", stream); - fputs ("\n# =======\n# options\n# =======\n", stream); - fprintf (stream, "opt -A -K -l -m -F %s\n", - calendar_week_begins_on_monday () ? "Monday" : "Sunday"); - fputs ("# Display week number (i.e. 1-52) on every Monday\n", stream); - fprintf (stream, "all monday in all %s %%w\n", _("Week")); - fputc ('\n', stream); -} - -/* Export footer. */ -static void -ical_export_footer (FILE *stream) -{ - fputs ("END:VCALENDAR\n", stream); -} - -static void -pcal_export_footer (FILE *stream) -{ -} - -/* Export recurrent events. */ -static void -ical_export_recur_events (FILE *stream) -{ - llist_item_t *i, *j; - char ical_date[BUFSIZ]; - - LLIST_FOREACH (&recur_elist, i) - { - struct recur_event *rev = LLIST_GET_DATA (i); - date_sec2date_fmt (rev->day, ICALDATEFMT, ical_date); - fputs ("BEGIN:VEVENT\n", stream); - fprintf (stream, "DTSTART:%s\n", ical_date); - fprintf (stream, "RRULE:FREQ=%s;INTERVAL=%d", - ical_recur_type[rev->rpt->type], rev->rpt->freq); - - if (rev->rpt->until != 0) - { - date_sec2date_fmt (rev->rpt->until, ICALDATEFMT, ical_date); - fprintf (stream, ";UNTIL=%s\n", ical_date); - } - else - fputc ('\n', stream); - - if (LLIST_FIRST (&rev->exc)) - { - fputs ("EXDATE:", stream); - LLIST_FOREACH (&rev->exc, j) - { - struct excp *exc = LLIST_GET_DATA (j); - date_sec2date_fmt (exc->st, ICALDATEFMT, ical_date); - fprintf (stream, "%s", ical_date); - if (LLIST_NEXT (j)) - fputc (',', stream); - else - fputc ('\n', stream); - } - } - - fprintf (stream, "SUMMARY:%s\n", rev->mesg); - fputs ("END:VEVENT\n", stream); - } -} - -/* Format and dump event data to a pcal formatted file. */ -static void -pcal_dump_event (FILE *stream, long event_date, long event_dur, - char *event_mesg) -{ - char pcal_date[BUFSIZ]; - - date_sec2date_fmt (event_date, "%b %d", pcal_date); - fprintf (stream, "%s %s\n", pcal_date, event_mesg); -} - -/* Format and dump appointment data to a pcal formatted file. */ -static void -pcal_dump_apoint (FILE *stream, long apoint_date, long apoint_dur, - char *apoint_mesg) -{ - char pcal_date[BUFSIZ], pcal_beg[BUFSIZ], pcal_end[BUFSIZ]; - - date_sec2date_fmt (apoint_date, "%b %d", pcal_date); - date_sec2date_fmt (apoint_date, "%R", pcal_beg); - date_sec2date_fmt (apoint_date + apoint_dur, "%R", pcal_end); - fprintf (stream, "%s ", pcal_date); - fprintf (stream, "(%s -> %s) %s\n", pcal_beg, pcal_end, apoint_mesg); -} - -static void -pcal_export_recur_events (FILE *stream) -{ - llist_item_t *i; - char pcal_date[BUFSIZ]; - - fputs ("\n# =============", stream); - fputs ("\n# Recur. Events", stream); - fputs ("\n# =============\n", stream); - fputs ("# (pcal does not support from..until dates specification\n", stream); - - LLIST_FOREACH (&recur_elist, i) - { - struct recur_event *rev = LLIST_GET_DATA (i); - if (rev->rpt->until == 0 && rev->rpt->freq == 1) - { - switch (rev->rpt->type) - { - case RECUR_DAILY: - date_sec2date_fmt (rev->day, "%b %d", pcal_date); - fprintf (stream, "all day on_or_after %s %s\n", pcal_date, - rev->mesg); - break; - case RECUR_WEEKLY: - date_sec2date_fmt (rev->day, "%a", pcal_date); - fprintf (stream, "all %s on_or_after ", pcal_date); - date_sec2date_fmt (rev->day, "%b %d", pcal_date); - fprintf (stream, "%s %s\n", pcal_date, rev->mesg); - break; - case RECUR_MONTHLY: - date_sec2date_fmt (rev->day, "%d", pcal_date); - fprintf (stream, "day on all %s %s\n", pcal_date, rev->mesg); - break; - case RECUR_YEARLY: - date_sec2date_fmt (rev->day, "%b %d", pcal_date); - fprintf (stream, "%s %s\n", pcal_date, rev->mesg); - break; - default: - EXIT (_("incoherent repetition type")); - } - } - else - { - const long YEAR_START = calendar_start_of_year (); - const long YEAR_END = calendar_end_of_year (); - - if (rev->day < YEAR_END && rev->day > YEAR_START) - foreach_date_dump (YEAR_END, rev->rpt, &rev->exc, rev->day, 0, - rev->mesg, (cb_dump_t) pcal_dump_event, stream); - } - } -} - -/* Export events. */ -static void -ical_export_events (FILE *stream) -{ - llist_item_t *i; - char ical_date[BUFSIZ]; - - LLIST_FOREACH (&eventlist, i) - { - struct event *ev = LLIST_TS_GET_DATA (i); - date_sec2date_fmt (ev->day, ICALDATEFMT, ical_date); - fputs ("BEGIN:VEVENT\n", stream); - fprintf (stream, "DTSTART:%s\n", ical_date); - fprintf (stream, "SUMMARY:%s\n", ev->mesg); - fputs ("END:VEVENT\n", stream); - } -} - -static void -pcal_export_events (FILE *stream) -{ - llist_item_t *i; - - fputs ("\n# ======\n# Events\n# ======\n", stream); - LLIST_FOREACH (&eventlist, i) - { - struct event *ev = LLIST_TS_GET_DATA (i); - pcal_dump_event (stream, ev->day, 0, ev->mesg); - } - fputc ('\n', stream); -} - -/* Export recurrent appointments. */ -static void -ical_export_recur_apoints (FILE *stream) -{ - llist_item_t *i, *j; - char ical_datetime[BUFSIZ]; - char ical_date[BUFSIZ]; - - LLIST_TS_LOCK (&recur_alist_p); - LLIST_TS_FOREACH (&recur_alist_p, i) - { - struct recur_apoint *rapt = LLIST_TS_GET_DATA (i); - - date_sec2date_fmt (rapt->start, ICALDATETIMEFMT, ical_datetime); - fputs ("BEGIN:VEVENT\n", stream); - fprintf (stream, "DTSTART:%s\n", ical_datetime); - fprintf (stream, "DURATION:PT0H0M%ldS\n", rapt->dur); - fprintf (stream, "RRULE:FREQ=%s;INTERVAL=%d", - ical_recur_type[rapt->rpt->type], rapt->rpt->freq); - - if (rapt->rpt->until != 0) - { - date_sec2date_fmt (rapt->rpt->until + HOURINSEC, ICALDATEFMT, - ical_date); - fprintf (stream, ";UNTIL=%s\n", ical_date); - } - else - fputc ('\n', stream); - - if (LLIST_FIRST (&rapt->exc)) - { - fputs ("EXDATE:", stream); - LLIST_FOREACH (&rapt->exc, j) - { - struct excp *exc = LLIST_GET_DATA (j); - date_sec2date_fmt (exc->st, ICALDATEFMT, ical_date); - fprintf (stream, "%s", ical_date); - if (LLIST_NEXT (j)) - fputc (',', stream); - else - fputc ('\n', stream); - } - } - - fprintf (stream, "SUMMARY:%s\n", rapt->mesg); - if (rapt->state & APOINT_NOTIFY) - ical_export_valarm (stream); - fputs ("END:VEVENT\n", stream); - } - LLIST_TS_UNLOCK (&recur_alist_p); -} - -static void -pcal_export_recur_apoints (FILE *stream) -{ - llist_item_t *i; - char pcal_date[BUFSIZ], pcal_beg[BUFSIZ], pcal_end[BUFSIZ]; - - fputs ("\n# ==============", stream); - fputs ("\n# Recur. Apoints", stream); - fputs ("\n# ==============\n", stream); - fputs ("# (pcal does not support from..until dates specification\n", stream); - - LLIST_TS_FOREACH (&recur_alist_p, i) - { - struct recur_apoint *rapt = LLIST_TS_GET_DATA (i); - - if (rapt->rpt->until == 0 && rapt->rpt->freq == 1) - { - date_sec2date_fmt (rapt->start, "%R", pcal_beg); - date_sec2date_fmt (rapt->start + rapt->dur, "%R", pcal_end); - switch (rapt->rpt->type) - { - case RECUR_DAILY: - date_sec2date_fmt (rapt->start, "%b %d", pcal_date); - fprintf (stream, "all day on_or_after %s (%s -> %s) %s\n", - pcal_date, pcal_beg, pcal_end, rapt->mesg); - break; - case RECUR_WEEKLY: - date_sec2date_fmt (rapt->start, "%a", pcal_date); - fprintf (stream, "all %s on_or_after ", pcal_date); - date_sec2date_fmt (rapt->start, "%b %d", pcal_date); - fprintf (stream, "%s (%s -> %s) %s\n", pcal_date, pcal_beg, - pcal_end, rapt->mesg); - break; - case RECUR_MONTHLY: - date_sec2date_fmt (rapt->start, "%d", pcal_date); - fprintf (stream, "day on all %s (%s -> %s) %s\n", pcal_date, - pcal_beg, pcal_end, rapt->mesg); - break; - case RECUR_YEARLY: - date_sec2date_fmt (rapt->start, "%b %d", pcal_date); - fprintf (stream, "%s (%s -> %s) %s\n", pcal_date, pcal_beg, - pcal_end, rapt->mesg); - break; - default: - EXIT (_("incoherent repetition type")); - } - } - else - { - const long YEAR_START = calendar_start_of_year (); - const long YEAR_END = calendar_end_of_year (); - - if (rapt->start < YEAR_END && rapt->start > YEAR_START) - foreach_date_dump (YEAR_END, rapt->rpt, &rapt->exc, rapt->start, - rapt->dur, rapt->mesg, - (cb_dump_t)pcal_dump_apoint, stream); - } - } -} - -/* Export appointments. */ -static void -ical_export_apoints (FILE *stream) -{ - llist_item_t *i; - char ical_datetime[BUFSIZ]; - - LLIST_TS_LOCK (&alist_p); - LLIST_TS_FOREACH (&alist_p, i) - { - struct apoint *apt = LLIST_TS_GET_DATA (i); - date_sec2date_fmt (apt->start, ICALDATETIMEFMT, ical_datetime); - fputs ("BEGIN:VEVENT\n", stream); - fprintf (stream, "DTSTART:%s\n", ical_datetime); - fprintf (stream, "DURATION:P%ldDT%ldH%ldM%ldS\n", - apt->dur / DAYINSEC, - (apt->dur / HOURINSEC) % DAYINHOURS, - (apt->dur / MININSEC) % HOURINMIN, - apt->dur % MININSEC); - fprintf (stream, "SUMMARY:%s\n", apt->mesg); - if (apt->state & APOINT_NOTIFY) - ical_export_valarm (stream); - fputs ("END:VEVENT\n", stream); - } - LLIST_TS_UNLOCK (&alist_p); -} - -static void -pcal_export_apoints (FILE *stream) -{ - llist_item_t *i; - - fputs ("\n# ============\n# Appointments\n# ============\n", stream); - LLIST_TS_LOCK (&alist_p); - LLIST_TS_FOREACH (&alist_p, i) - { - struct apoint *apt = LLIST_TS_GET_DATA (i); - pcal_dump_apoint (stream, apt->start, apt->dur, apt->mesg); - } - LLIST_TS_UNLOCK (&alist_p); - fputc ('\n', stream); -} - -/* Export todo items. */ -static void -ical_export_todo (FILE *stream) -{ - llist_item_t *i; - - LLIST_FOREACH (&todolist, i) - { - struct todo *todo = LLIST_TS_GET_DATA (i); - if (todo->id < 0) /* completed items */ - continue; - - fputs ("BEGIN:VTODO\n", stream); - fprintf (stream, "PRIORITY:%d\n", todo->id); - fprintf (stream, "SUMMARY:%s\n", todo->mesg); - fputs ("END:VTODO\n", stream); - } -} - -static void -pcal_export_todo (FILE *stream) -{ - llist_item_t *i; - - fputs ("#\n# Todos\n#\n", stream); - LLIST_FOREACH (&todolist, i) - { - struct todo *todo = LLIST_TS_GET_DATA (i); - if (todo->id < 0) /* completed items */ - continue; - - fputs ("note all ", stream); - fprintf (stream, "%d. %s\n", todo->id, todo->mesg); - } - fputc ('\n', stream); -} - /* Append a line to a file. */ unsigned io_fprintln (const char *fname, const char *fmt, ...) @@ -1509,26 +1008,10 @@ io_export_data (enum export_type type) if (stream == NULL) return; - cb_export_header[type] (stream); - - if (!conf.skip_progress_bar && ui_mode == UI_CURSES) - progress_bar (PROGRESS_BAR_EXPORT, PROGRESS_BAR_EXPORT_EVENTS); - cb_export_recur_events[type] (stream); - cb_export_events[type] (stream); - - if (!conf.skip_progress_bar && ui_mode == UI_CURSES) - progress_bar (PROGRESS_BAR_EXPORT, PROGRESS_BAR_EXPORT_APOINTS); - cb_export_recur_apoints[type] (stream); - cb_export_apoints[type] (stream); - - if (!conf.skip_progress_bar && ui_mode == UI_CURSES) - progress_bar (PROGRESS_BAR_EXPORT, PROGRESS_BAR_EXPORT_TODO); - cb_export_todo[type] (stream); - - cb_export_footer[type] (stream); - - if (stream != stdout) - file_close (stream, __FILE_POS__); + if (type == IO_EXPORT_ICAL) + ical_export_data (stream); + else if (type == IO_EXPORT_PCAL) + pcal_export_data (stream); if (!conf.skip_system_dialogs && ui_mode == UI_CURSES) { @@ -1561,961 +1044,6 @@ io_export_bar (void) wins_doupdate (); } -/* Print a header to describe import log report format. */ -static void -ical_log_init (FILE *log, float version) -{ - const char *header = - "+-------------------------------------------------------------------+\n" - "| Calcurse icalendar import log. |\n" - "| |\n" - "| Items imported from icalendar file, version %1.1f |\n" - "| Some items could not be imported, they are described hereafter. |\n" - "| The log line format is as follows: |\n" - "| |\n" - "| TYPE [LINE]: DESCRIPTION |\n" - "| |\n" - "| where: |\n" - "| * TYPE represents the item type ('VEVENT' or 'VTODO') |\n" - "| * LINE is the line in the input stream at which this item begins |\n" - "| * DESCRIPTION indicates why the item could not be imported |\n" - "+-------------------------------------------------------------------+\n\n"; - - if (log) - fprintf (log, header, version); -} - -/* - * Used to build a report of the import process. - * The icalendar item for which a problem occurs is mentioned (by giving its - * first line inside the icalendar file), together with a message describing the - * problem. - */ -static void -ical_log (FILE *log, ical_types_e type, unsigned lineno, char *msg) -{ - const char *typestr[ICAL_TYPES] = {"VEVENT", "VTODO"}; - - RETURN_IF (type < 0 || type >= ICAL_TYPES, _("unknown ical type")); - if (log) - fprintf (log, "%s [%d]: %s\n", typestr[type], lineno, msg); -} - -static void -ical_store_todo (int priority, char *mesg, char *note) -{ - todo_add (mesg, priority, note); - mem_free (mesg); - erase_note (¬e); -} - -static void -ical_store_event (char *mesg, char *note, long day, long end, ical_rpt_t *rpt, - llist_t *exc) -{ - const int EVENTID = 1; - - if (rpt) - { - recur_event_new (mesg, note, day, EVENTID, rpt->type, rpt->freq, - rpt->until, exc); - mem_free (rpt); - } - else if (end && end != day) - { - /* Here we have an event that spans over several days. */ - rpt = mem_malloc (sizeof (ical_rpt_t)); - rpt->type = RECUR_DAILY; - rpt->freq = 1; - rpt->count = 0; - rpt->until = end; - recur_event_new (mesg, note, day, EVENTID, rpt->type, rpt->freq, - rpt->until, exc); - mem_free (rpt); - } - else - { - event_new (mesg, note, day, EVENTID); - } - mem_free (mesg); - erase_note (¬e); -} - -static void -ical_store_apoint (char *mesg, char *note, long start, long dur, - ical_rpt_t *rpt, llist_t *exc, int has_alarm) -{ - char state = 0L; - - if (has_alarm) - state |= APOINT_NOTIFY; - if (rpt) - { - recur_apoint_new (mesg, note, start, dur, state, rpt->type, rpt->freq, - rpt->until, exc); - mem_free (rpt); - } - else - { - apoint_new (mesg, note, start, dur, state); - } - mem_free (mesg); - erase_note (¬e); -} - -/* - * Returns an allocated string representing the string given in argument once - * unformatted. - * - * Note: - * Even if the RFC2445 recommends not to have more than 75 octets on one line of - * text, I prefer not to restrict the parsing to this size, thus I use a buffer - * of size BUFSIZ. - * - * Extract from RFC2445: - * Lines of text SHOULD NOT be longer than 75 octets, excluding the line - * break. - */ -static char * -ical_unformat_line (char *line) -{ - char *p, uline[BUFSIZ]; - int len; - - if (strlen (line) >= BUFSIZ) - return NULL; - - bzero (uline, BUFSIZ); - for (len = 0, p = line; *p; p++) - { - switch (*p) - { - case '\\': - switch (*(p + 1)) - { - case 'n': - uline[len++] = '\n'; - p++; - break; - case 't': - uline[len++] = '\t'; - p++; - break; - case ';': - case ':': - case ',': - uline[len++] = *(p + 1); - p++; - break; - default: - uline[len++] = *p; - break; - } - break; - default: - uline[len++] = *p; - break; - } - } - - return mem_strdup (uline); -} - -static void -ical_readline_init (FILE *fdi, char *buf, char *lstore, unsigned *ln) -{ - char *eol; - - *buf = *lstore = '\0'; - fgets (lstore, BUFSIZ, fdi); - if ((eol = strchr(lstore, '\n')) != NULL) - *eol = '\0'; - (*ln)++; -} - -static int -ical_readline (FILE *fdi, char *buf, char *lstore, unsigned *ln) -{ - char *eol; - - strncpy (buf, lstore, BUFSIZ); - (*ln)++; - - while (fgets (lstore, BUFSIZ, fdi) != NULL) - { - if ((eol = strchr(lstore, '\n')) != NULL) - *eol = '\0'; - if (*lstore != SPACE && *lstore != TAB) - break; - strncat (buf, lstore + 1, BUFSIZ); - buf[BUFSIZ - 1] = '\0'; - (*ln)++; - } - - if (feof (fdi)) - { - *lstore = '\0'; - if (*buf == '\0') - return 0; - } - - return 1; -} - -static float -ical_chk_header (FILE *fd, char *buf, char *lstore, unsigned *lineno) -{ - const int HEADER_MALFORMED = -1; - const struct string icalheader = STRING_BUILD ("BEGIN:VCALENDAR"); - float version; - - if (!ical_readline (fd, buf, lstore, lineno)) - return HEADER_MALFORMED; - - str_toupper (buf); - if (strncmp (buf, icalheader.str, icalheader.len) != 0) - return HEADER_MALFORMED; - - while (!sscanf (buf, "VERSION:%f", &version)) - { - if (!ical_readline (fd, buf, lstore, lineno)) - return HEADER_MALFORMED; - } - return version; -} - -/* - * iCalendar date-time format is based on the ISO 8601 complete - * representation. It should be something like : DATE 'T' TIME - * where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'. - * The time and 'T' separator are optional (in the case of an day-long event). - * - * Optionnaly, if the type pointer is given, specify if it is an event - * (no time is given, meaning it is an all-day event), or an appointment - * (time is given). - * - * The timezone is not yet handled by calcurse. - */ -static long -ical_datetime2long (char *datestr, ical_vevent_e *type) -{ - const int NOTFOUND = 0, FORMAT_DATE = 3, FORMAT_DATETIME = 5; - struct date date; - unsigned hour, min; - long datelong; - int format; - - format = sscanf (datestr, "%04u%02u%02uT%02u%02u", - &date.yyyy, &date.mm, &date.dd, &hour, &min); - if (format == FORMAT_DATE) - { - if (type) - *type = EVENT; - datelong = date2sec (date, 0, 0); - } - else if (format == FORMAT_DATETIME) - { - if (type) - *type = APPOINTMENT; - datelong = date2sec (date, hour, min); - } - else - { - datelong = NOTFOUND; - } - return datelong; -} - -static long -ical_durtime2long (char *timestr) -{ - long timelong; - char *p; - - if ((p = strchr (timestr, 'T')) == NULL) - timelong = 0; - else - { - int nbmatch; - struct { - unsigned hour, min, sec; - } time; - - p++; - bzero (&time, sizeof time); - nbmatch = sscanf (p, "%uH%uM%uS", &time.hour, &time.min, &time.sec); - if (nbmatch < 1 || nbmatch > 3) - timelong = 0; - else - timelong = time.hour * HOURINSEC + time.min * MININSEC + time.sec; - } - return timelong; -} - -/* - * Extract from RFC2445: - * - * Value Name: DURATION - * - * Purpose: This value type is used to identify properties that contain - * duration of time. - * - * Formal Definition: The value type is defined by the following - * notation: - * - * dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week) - * dur-date = dur-day [dur-time] - * dur-time = "T" (dur-hour / dur-minute / dur-second) - * dur-week = 1*DIGIT "W" - * dur-hour = 1*DIGIT "H" [dur-minute] - * dur-minute = 1*DIGIT "M" [dur-second] - * dur-second = 1*DIGIT "S" - * dur-day = 1*DIGIT "D" - * - * Example: A duration of 15 days, 5 hours and 20 seconds would be: - * P15DT5H0M20S - * A duration of 7 weeks would be: - * P7W - */ -static long -ical_dur2long (char *durstr) -{ - const int NOTFOUND = -1; - long durlong; - char *p; - struct { - unsigned week, day; - } date; - - bzero (&date, sizeof date); - if ((p = strchr (durstr, 'P')) == NULL) - durlong = NOTFOUND; - else - { - p++; - if (*p == '-') - return NOTFOUND; - else if (*p == '+') - p++; - - if (*p == 'T') /* dur-time */ - durlong = ical_durtime2long (p); - else if (strchr (p, 'W')) /* dur-week */ - { - if (sscanf (p, "%u", &date.week) == 1) - durlong = date.week * WEEKINDAYS * DAYINSEC; - else - durlong = NOTFOUND; - } - else - { - if (strchr (p, 'D')) /* dur-date */ - { - if (sscanf (p, "%uD", &date.day) == 1) - { - durlong = date.day * DAYINSEC; - durlong += ical_durtime2long (p); - } - else - durlong = NOTFOUND; - } - else - durlong = NOTFOUND; - } - } - return durlong; -} - -/* - * Compute the vevent repetition end date from the repetition count. - * - * Extract from RFC2445: - * The COUNT rule part defines the number of occurrences at which to - * range-bound the recurrence. The "DTSTART" property value, if specified, - * counts as the first occurrence. - */ -static long -ical_compute_rpt_until (long start, ical_rpt_t *rpt) -{ - long until; - - switch (rpt->type) - { - case RECUR_DAILY: - until = date_sec_change (start, 0, rpt->freq * (rpt->count - 1)); - break; - case RECUR_WEEKLY: - until = date_sec_change (start, 0, - rpt->freq * WEEKINDAYS * (rpt->count - 1)); - break; - case RECUR_MONTHLY: - until = date_sec_change (start, rpt->freq * (rpt->count - 1), 0); - break; - case RECUR_YEARLY: - until = date_sec_change (start, rpt->freq * 12 * (rpt->count - 1), 0); - break; - default: - until = 0; - break; - /* NOTREACHED */ - } - return until; -} - -/* - * Read a recurrence rule from an iCalendar RRULE string. - * - * Value Name: RECUR - * - * Purpose: This value type is used to identify properties that contain - * a recurrence rule specification. - * - * Formal Definition: The value type is defined by the following - * notation: - * - * recur = "FREQ"=freq *( - * - * ; either UNTIL or COUNT may appear in a 'recur', - * ; but UNTIL and COUNT MUST NOT occur in the same 'recur' - * - * ( ";" "UNTIL" "=" enddate ) / - * ( ";" "COUNT" "=" 1*DIGIT ) / - * - * ; the rest of these keywords are optional, - * ; but MUST NOT occur more than - * ; once - * - * ( ";" "INTERVAL" "=" 1*DIGIT ) / - * ( ";" "BYSECOND" "=" byseclist ) / - * ( ";" "BYMINUTE" "=" byminlist ) / - * ( ";" "BYHOUR" "=" byhrlist ) / - * ( ";" "BYDAY" "=" bywdaylist ) / - * ( ";" "BYMONTHDAY" "=" bymodaylist ) / - * ( ";" "BYYEARDAY" "=" byyrdaylist ) / - * ( ";" "BYWEEKNO" "=" bywknolist ) / - * ( ";" "BYMONTH" "=" bymolist ) / - * ( ";" "BYSETPOS" "=" bysplist ) / - * ( ";" "WKST" "=" weekday ) / - * ( ";" x-name "=" text ) - * ) -*/ -static ical_rpt_t * -ical_read_rrule (FILE *log, char *rrulestr, unsigned *noskipped, - const int itemline) -{ - const struct string daily = STRING_BUILD ("DAILY"); - const struct string weekly = STRING_BUILD ("WEEKLY"); - const struct string monthly = STRING_BUILD ("MONTHLY"); - const struct string yearly = STRING_BUILD ("YEARLY"); - const struct string count = STRING_BUILD ("COUNT="); - const struct string interv = STRING_BUILD ("INTERVAL="); - unsigned interval; - ical_rpt_t *rpt; - char *p; - - rpt = NULL; - if ((p = strchr (rrulestr, ':')) != NULL) - { - char freqstr[BUFSIZ]; - - p++; - rpt = mem_malloc (sizeof (ical_rpt_t)); - bzero (rpt, sizeof (ical_rpt_t)); - if (sscanf (p, "FREQ=%s", freqstr) != 1) - { - ical_log (log, ICAL_VEVENT, itemline, - _("recurrence frequence not found.")); - (*noskipped)++; - mem_free (rpt); - return NULL; - } - else - { - if (strncmp (freqstr, daily.str, daily.len) == 0) - rpt->type = RECUR_DAILY; - else if (strncmp (freqstr, weekly.str, weekly.len) == 0) - rpt->type = RECUR_WEEKLY; - else if (strncmp (freqstr, monthly.str, monthly.len) == 0) - rpt->type = RECUR_MONTHLY; - else if (strncmp (freqstr, yearly.str, yearly.len) == 0) - rpt->type = RECUR_YEARLY; - else - { - ical_log (log, ICAL_VEVENT, itemline, - _("recurrence frequence not recognized.")); - (*noskipped)++; - mem_free (rpt); - return NULL; - } - } - /* - The UNTIL rule part defines a date-time value which bounds the - recurrence rule in an inclusive manner. If not present, and the - COUNT rule part is also not present, the RRULE is considered to - repeat forever. - - The COUNT rule part defines the number of occurrences at which to - range-bound the recurrence. The "DTSTART" property value, if - specified, counts as the first occurrence. - */ - if ((p = strstr (rrulestr, "UNTIL")) != NULL) - { - char *untilstr; - - untilstr = strchr (p, '='); - rpt->until = ical_datetime2long (++untilstr, NULL); - } - else - { - unsigned cnt; - char *countstr; - - if ((countstr = strstr (rrulestr, count.str)) != NULL) - { - countstr += count.len; - if (sscanf (countstr, "%u", &cnt) != 1) - { - rpt->until = 0; - /* endless repetition */ - } - else - { - rpt->count = cnt; - } - } - else - rpt->until = 0; - } - - if ((p = strstr (rrulestr, interv.str)) != NULL) - { - p += interv.len; - if (sscanf (p, "%u", &interval) != 1) - { - rpt->freq = 1; - /* default frequence if none specified */ - } - else - { - rpt->freq = interval; - } - } - else - { - rpt->freq = 1; - } - } - else - { - ical_log (log, ICAL_VEVENT, itemline, _("recurrence rule malformed.")); - (*noskipped)++; - } - return rpt; -} - -static void -ical_add_exc (llist_t *exc_head, long date) -{ - if (date != 0) - { - struct excp *exc = mem_malloc (sizeof (struct excp)); - exc->st = date; - - LLIST_ADD (exc_head, exc); - } -} - -/* - * This property defines the list of date/time exceptions for a - * recurring calendar component. - */ -void -ical_read_exdate (llist_t *exc, FILE *log, char *exstr, unsigned *noskipped, - const int itemline) -{ - char *p, *q; - long date; - - LLIST_INIT (exc); - if ((p = strchr (exstr, ':')) != NULL) - { - p++; - while ((q = strchr (p, ',')) != NULL) - { - char buf[BUFSIZ]; - const int buflen = q - p; - - strncpy (buf, p, buflen); - buf[buflen] = '\0'; - date = ical_datetime2long (buf, NULL); - ical_add_exc (exc, date); - p = ++q; - } - date = ical_datetime2long (p, NULL); - ical_add_exc (exc, date); - } - else - { - ical_log (log, ICAL_VEVENT, itemline, - _("recurrence exception dates malformed.")); - (*noskipped)++; - } -} - -/* Return an allocated string containing the name of the newly created note. */ -static char * -ical_read_note (char *line, unsigned *noskipped, ical_vevent_e item_type, - const int itemline, FILE *log) -{ - char *sha1 = mem_malloc (SHA1_DIGESTLEN * 2 + 1); - char *p, *notestr, fullnotename[BUFSIZ]; - FILE *fdo; - - if ((p = strchr (line, ':')) != NULL) - { - p++; - notestr = ical_unformat_line (p); - if (notestr == NULL) - { - ical_log (log, item_type, itemline, - _("could not get entire item description.")); - (*noskipped)++; - return NULL; - } - else if (strlen (notestr) == 0) - { - mem_free (notestr); - return NULL; - } - else - { - sha1_digest (notestr, sha1); - snprintf (fullnotename, BUFSIZ, "%s%s", path_notes, sha1); - fdo = fopen (fullnotename, "w"); - EXIT_IF (fdo == NULL, _("Warning: could not open %s, Aborting..."), - fullnotename); - fprintf (fdo, "%s", notestr); - file_close (fdo, __FILE_POS__); - mem_free (notestr); - return sha1; - } - } - else - { - ical_log (log, item_type, itemline, _("description malformed.")); - (*noskipped)++; - return NULL; - } -} - -/* Returns an allocated string containing the ical item summary. */ -static char * -ical_read_summary (char *line) -{ - char *p, *summary; - - if ((p = strchr (line, ':')) != NULL) - { - p++; - summary = ical_unformat_line (p); - return summary; - } - else - return NULL; -} - -static void -ical_read_event (FILE *fdi, FILE *log, unsigned *noevents, unsigned *noapoints, - unsigned *noskipped, char *buf, char *lstore, - unsigned *lineno) -{ - const int ITEMLINE = *lineno; - const struct string endevent = STRING_BUILD ("END:VEVENT"); - const struct string summary = STRING_BUILD ("SUMMARY"); - const struct string dtstart = STRING_BUILD ("DTSTART"); - const struct string dtend = STRING_BUILD ("DTEND"); - const struct string duration = STRING_BUILD ("DURATION"); - const struct string rrule = STRING_BUILD ("RRULE"); - const struct string exdate = STRING_BUILD ("EXDATE"); - const struct string alarm = STRING_BUILD ("BEGIN:VALARM"); - const struct string endalarm = STRING_BUILD ("END:VALARM"); - const struct string desc = STRING_BUILD ("DESCRIPTION"); - ical_vevent_e vevent_type; - char *p, buf_upper[BUFSIZ]; - struct { - llist_t exc; - ical_rpt_t *rpt; - char *mesg, *note; - long start, end, dur; - int has_alarm; - } vevent; - int skip_alarm; - - vevent_type = UNDEFINED; - bzero (&vevent, sizeof vevent); - skip_alarm = 0; - while (ical_readline (fdi, buf, lstore, lineno)) - { - strncpy (buf_upper, buf, BUFSIZ); - buf_upper[BUFSIZ - 1] = '\0'; - str_toupper (buf_upper); - - if (skip_alarm) - { - /* Need to skip VALARM properties because some keywords could - interfere, such as DURATION, SUMMARY,.. */ - if (strncmp (buf_upper, endalarm.str, endalarm.len) == 0) - skip_alarm = 0; - continue; - } - if (strncmp (buf_upper, endevent.str, endevent.len) == 0) - { - if (vevent.mesg) - { - if (vevent.rpt && vevent.rpt->count) - vevent.rpt->until = ical_compute_rpt_until (vevent.start, - vevent.rpt); - - switch (vevent_type) - { - case APPOINTMENT: - if (vevent.start == 0) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("appointment has no start time.")); - goto cleanup; - } - if (vevent.dur == 0) - { - if (vevent.end == 0) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("could not compute duration " - "(no end time).")); - goto cleanup; - } - else if (vevent.start == vevent.end) - { - vevent_type = EVENT; - vevent.end = 0L; - ical_store_event (vevent.mesg, vevent.note, - vevent.start, vevent.end, - vevent.rpt, &vevent.exc); - (*noevents)++; - return; - } - else - { - vevent.dur = vevent.end - vevent.start; - if (vevent.dur < 0) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("item has a negative duration.")); - goto cleanup; - } - } - } - ical_store_apoint (vevent.mesg, vevent.note, vevent.start, - vevent.dur, vevent.rpt, &vevent.exc, - vevent.has_alarm); - (*noapoints)++; - break; - case EVENT: - if (vevent.start == 0) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("event date is not defined.")); - goto cleanup; - } - ical_store_event (vevent.mesg, vevent.note, vevent.start, - vevent.end, vevent.rpt, &vevent.exc); - (*noevents)++; - break; - case UNDEFINED: - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("item could not be identified.")); - goto cleanup; - break; - } - } - else - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("could not retrieve item summary.")); - goto cleanup; - } - return; - } - else - { - if (strncmp (buf_upper, dtstart.str, dtstart.len) == 0) - { - if ((p = strchr (buf, ':')) != NULL) - vevent.start = ical_datetime2long (++p, &vevent_type); - if (!vevent.start) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("could not retrieve event start time.")); - goto cleanup; - } - } - else if (strncmp (buf_upper, dtend.str, dtend.len) == 0) - { - if ((p = strchr (buf, ':')) != NULL) - vevent.end = ical_datetime2long (++p, &vevent_type); - if (!vevent.end) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("could not retrieve event end time.")); - goto cleanup; - } - } - else if (strncmp (buf_upper, duration.str, duration.len) == 0) - { - if ((vevent.dur = ical_dur2long (buf)) <= 0) - { - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("item duration malformed.")); - goto cleanup; - } - } - else if (strncmp (buf_upper, rrule.str, rrule.len) == 0) - { - vevent.rpt = ical_read_rrule (log, buf, noskipped, ITEMLINE); - } - else if (strncmp (buf_upper, exdate.str, exdate.len) == 0) - { - ical_read_exdate (&vevent.exc, log, buf, noskipped, ITEMLINE); - } - else if (strncmp (buf_upper, summary.str, summary.len) == 0) - { - vevent.mesg = ical_read_summary (buf); - } - else if (strncmp (buf_upper, alarm.str, alarm.len) == 0) - { - skip_alarm = 1; - vevent.has_alarm = 1; - } - else if (strncmp (buf_upper, desc.str, desc.len) == 0) - { - vevent.note = ical_read_note (buf, noskipped, ICAL_VEVENT, - ITEMLINE, log); - } - } - } - ical_log (log, ICAL_VEVENT, ITEMLINE, - _("The ical file seems to be malformed. " - "The end of item was not found.")); - -cleanup: - - if (vevent.note) - mem_free (vevent.note); - if (vevent.mesg) - mem_free (vevent.mesg); - if (vevent.rpt) - mem_free (vevent.rpt); - LLIST_FREE (&vevent.exc); - (*noskipped)++; -} - -static void -ical_read_todo (FILE *fdi, FILE *log, unsigned *notodos, unsigned *noskipped, - char *buf, char *lstore, unsigned *lineno) -{ - const struct string endtodo = STRING_BUILD ("END:VTODO"); - const struct string summary = STRING_BUILD ("SUMMARY"); - const struct string alarm = STRING_BUILD ("BEGIN:VALARM"); - const struct string endalarm = STRING_BUILD ("END:VALARM"); - const struct string desc = STRING_BUILD ("DESCRIPTION"); - const int LOWEST = 9; - const int ITEMLINE = *lineno; - char buf_upper[BUFSIZ]; - struct { - char *mesg, *note; - int has_priority, priority; - } vtodo; - int skip_alarm; - - bzero (&vtodo, sizeof vtodo); - skip_alarm = 0; - while (ical_readline (fdi, buf, lstore, lineno)) - { - strncpy (buf_upper, buf, BUFSIZ); - buf_upper[BUFSIZ - 1] = '\0'; - str_toupper (buf_upper); - if (skip_alarm) - { - /* Need to skip VALARM properties because some keywords could - interfere, such as DURATION, SUMMARY,.. */ - if (strncmp (buf_upper, endalarm.str, endalarm.len) == 0) - skip_alarm = 0; - continue; - } - if (strncmp (buf_upper, endtodo.str, endtodo.len) == 0) - { - if (!vtodo.has_priority) - vtodo.priority = LOWEST; - if (vtodo.mesg) - { - ical_store_todo (vtodo.priority, vtodo.mesg, vtodo.note); - (*notodos)++; - } - else - { - ical_log (log, ICAL_VTODO, ITEMLINE, - _("could not retrieve item summary.")); - goto cleanup; - } - return; - } - else - { - int tmpint; - - if (sscanf (buf_upper, "PRIORITY:%d", &tmpint) == 1) - { - if (tmpint <= 9 && tmpint >= 1) - { - vtodo.priority = tmpint; - vtodo.has_priority = 1; - } - else - { - ical_log (log, ICAL_VTODO, ITEMLINE, - _("item priority is not acceptable " - "(must be between 1 and 9).")); - vtodo.priority = LOWEST; - } - } - else if (strncmp (buf_upper, summary.str, summary.len) == 0) - { - vtodo.mesg = ical_read_summary (buf); - } - else if (strncmp (buf_upper, alarm.str, alarm.len) == 0) - { - skip_alarm = 1; - } - else if (strncmp (buf_upper, desc.str, desc.len) == 0) - { - vtodo.note = ical_read_note (buf, noskipped, ICAL_VTODO, - ITEMLINE, log); - } - } - } - ical_log (log, ICAL_VTODO, ITEMLINE, - _("The ical file seems to be malformed. " - "The end of item was not found.")); - -cleanup: - - if (vtodo.note) - mem_free (vtodo.note); - if (vtodo.mesg) - mem_free (vtodo.mesg); - (*noskipped)++; -} - static FILE * get_import_stream (enum export_type type) { @@ -2560,14 +1088,10 @@ get_import_stream (enum export_type type) void io_import_data (enum import_type type, char *stream_name) { - const struct string vevent = STRING_BUILD ("BEGIN:VEVENT"); - const struct string vtodo = STRING_BUILD ("BEGIN:VTODO"); char *proc_report = _("Import process report: %04d lines read "); char stats_str[4][BUFSIZ]; - char buf[BUFSIZ], lstore[BUFSIZ]; FILE *stream = NULL; struct io_file *log; - float ical_version; struct { unsigned events, apoints, todos, lines, skipped; } stats; @@ -2593,11 +1117,6 @@ io_import_data (enum import_type type, char *stream_name) return; bzero (&stats, sizeof stats); - ical_readline_init (stream, buf, lstore, &stats.lines); - ical_version = ical_chk_header (stream, buf, lstore, &stats.lines); - RETURN_IF (ical_version < 0, - _("Warning: ical header malformed or wrong version number. " - "Aborting...")); log = io_log_init (); if (log == NULL) @@ -2606,23 +1125,11 @@ io_import_data (enum import_type type, char *stream_name) file_close (stream, __FILE_POS__); return; } - ical_log_init (log->fd, ical_version); - while (ical_readline (stream, buf, lstore, &stats.lines)) - { - stats.lines++; - str_toupper (buf); - if (strncmp (buf, vevent.str, vevent.len) == 0) - { - ical_read_event (stream, log->fd, &stats.events, &stats.apoints, - &stats.skipped, buf, lstore, &stats.lines); - } - else if (strncmp (buf, vtodo.str, vtodo.len) == 0) - { - ical_read_todo (stream, log->fd, &stats.todos, &stats.skipped, - buf, lstore, &stats.lines); - } - } + if (type == IO_IMPORT_ICAL) + ical_import_data (stream, log->fd, &stats.events, &stats.apoints, + &stats.todos, &stats.lines, &stats.skipped); + if (stream != stdin) file_close (stream, __FILE_POS__); diff --git a/src/pcal.c b/src/pcal.c new file mode 100644 index 0000000..0046739 --- /dev/null +++ b/src/pcal.c @@ -0,0 +1,317 @@ +/* + * Calcurse - text-based organizer + * + * Copyright (c) 2004-2011 calcurse Development Team <misc@calcurse.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Send your feedback or comments to : misc@calcurse.org + * Calcurse home page : http://calcurse.org + * + */ + +#include <sys/types.h> + +#include "calcurse.h" + +/* Static functions used to add export functionalities. */ +static void pcal_export_header (FILE *); +static void pcal_export_recur_events (FILE *); +static void pcal_export_events (FILE *); +static void pcal_export_recur_apoints (FILE *); +static void pcal_export_apoints (FILE *); +static void pcal_export_todo (FILE *); +static void pcal_export_footer (FILE *); + +/* Type definition for callbacks to export functions. */ +typedef void (*cb_dump_t)(FILE *, long, long, char *); + +/* + * Travel through each occurence of an item, and execute the given callback + * (mainly used to export data). + */ +static void +foreach_date_dump (const long date_end, struct rpt *rpt, llist_t *exc, + long item_first_date, long item_dur, char *item_mesg, + cb_dump_t cb_dump, FILE *stream) +{ + long date, item_time; + struct tm lt; + time_t t; + + t = item_first_date; + lt = *localtime (&t); + lt.tm_hour = lt.tm_min = lt.tm_sec = 0; + lt.tm_isdst = -1; + date = mktime (<); + item_time = item_first_date - date; + + while (date <= date_end && date <= rpt->until) + { + if (recur_item_inday (item_first_date, item_dur, exc, rpt->type, + rpt->freq, rpt->until, date)) + { + (*cb_dump)(stream, date + item_time, item_dur, item_mesg); + } + switch (rpt->type) + { + case RECUR_DAILY: + date = date_sec_change (date, 0, rpt->freq); + break; + case RECUR_WEEKLY: + date = date_sec_change (date, 0, rpt->freq * WEEKINDAYS); + break; + case RECUR_MONTHLY: + date = date_sec_change (date, rpt->freq, 0); + break; + case RECUR_YEARLY: + date = date_sec_change (date, rpt->freq * 12, 0); + break; + default: + EXIT (_("incoherent repetition type")); + /* NOTREACHED */ + break; + } + } +} + +static void +pcal_export_header (FILE *stream) +{ + fputs ("# calcurse pcal export\n", stream); + fputs ("\n# =======\n# options\n# =======\n", stream); + fprintf (stream, "opt -A -K -l -m -F %s\n", + calendar_week_begins_on_monday () ? "Monday" : "Sunday"); + fputs ("# Display week number (i.e. 1-52) on every Monday\n", stream); + fprintf (stream, "all monday in all %s %%w\n", _("Week")); + fputc ('\n', stream); +} + +static void +pcal_export_footer (FILE *stream) +{ +} + +/* Format and dump event data to a pcal formatted file. */ +static void +pcal_dump_event (FILE *stream, long event_date, long event_dur, + char *event_mesg) +{ + char pcal_date[BUFSIZ]; + + date_sec2date_fmt (event_date, "%b %d", pcal_date); + fprintf (stream, "%s %s\n", pcal_date, event_mesg); +} + +/* Format and dump appointment data to a pcal formatted file. */ +static void +pcal_dump_apoint (FILE *stream, long apoint_date, long apoint_dur, + char *apoint_mesg) +{ + char pcal_date[BUFSIZ], pcal_beg[BUFSIZ], pcal_end[BUFSIZ]; + + date_sec2date_fmt (apoint_date, "%b %d", pcal_date); + date_sec2date_fmt (apoint_date, "%R", pcal_beg); + date_sec2date_fmt (apoint_date + apoint_dur, "%R", pcal_end); + fprintf (stream, "%s ", pcal_date); + fprintf (stream, "(%s -> %s) %s\n", pcal_beg, pcal_end, apoint_mesg); +} + +static void +pcal_export_recur_events (FILE *stream) +{ + llist_item_t *i; + char pcal_date[BUFSIZ]; + + fputs ("\n# =============", stream); + fputs ("\n# Recur. Events", stream); + fputs ("\n# =============\n", stream); + fputs ("# (pcal does not support from..until dates specification\n", stream); + + LLIST_FOREACH (&recur_elist, i) + { + struct recur_event *rev = LLIST_GET_DATA (i); + if (rev->rpt->until == 0 && rev->rpt->freq == 1) + { + switch (rev->rpt->type) + { + case RECUR_DAILY: + date_sec2date_fmt (rev->day, "%b %d", pcal_date); + fprintf (stream, "all day on_or_after %s %s\n", pcal_date, + rev->mesg); + break; + case RECUR_WEEKLY: + date_sec2date_fmt (rev->day, "%a", pcal_date); + fprintf (stream, "all %s on_or_after ", pcal_date); + date_sec2date_fmt (rev->day, "%b %d", pcal_date); + fprintf (stream, "%s %s\n", pcal_date, rev->mesg); + break; + case RECUR_MONTHLY: + date_sec2date_fmt (rev->day, "%d", pcal_date); + fprintf (stream, "day on all %s %s\n", pcal_date, rev->mesg); + break; + case RECUR_YEARLY: + date_sec2date_fmt (rev->day, "%b %d", pcal_date); + fprintf (stream, "%s %s\n", pcal_date, rev->mesg); + break; + default: + EXIT (_("incoherent repetition type")); + } + } + else + { + const long YEAR_START = calendar_start_of_year (); + const long YEAR_END = calendar_end_of_year (); + + if (rev->day < YEAR_END && rev->day > YEAR_START) + foreach_date_dump (YEAR_END, rev->rpt, &rev->exc, rev->day, 0, + rev->mesg, (cb_dump_t) pcal_dump_event, stream); + } + } +} + +static void +pcal_export_events (FILE *stream) +{ + llist_item_t *i; + + fputs ("\n# ======\n# Events\n# ======\n", stream); + LLIST_FOREACH (&eventlist, i) + { + struct event *ev = LLIST_TS_GET_DATA (i); + pcal_dump_event (stream, ev->day, 0, ev->mesg); + } + fputc ('\n', stream); +} + +static void +pcal_export_recur_apoints (FILE *stream) +{ + llist_item_t *i; + char pcal_date[BUFSIZ], pcal_beg[BUFSIZ], pcal_end[BUFSIZ]; + + fputs ("\n# ==============", stream); + fputs ("\n# Recur. Apoints", stream); + fputs ("\n# ==============\n", stream); + fputs ("# (pcal does not support from..until dates specification\n", stream); + + LLIST_TS_FOREACH (&recur_alist_p, i) + { + struct recur_apoint *rapt = LLIST_TS_GET_DATA (i); + + if (rapt->rpt->until == 0 && rapt->rpt->freq == 1) + { + date_sec2date_fmt (rapt->start, "%R", pcal_beg); + date_sec2date_fmt (rapt->start + rapt->dur, "%R", pcal_end); + switch (rapt->rpt->type) + { + case RECUR_DAILY: + date_sec2date_fmt (rapt->start, "%b %d", pcal_date); + fprintf (stream, "all day on_or_after %s (%s -> %s) %s\n", + pcal_date, pcal_beg, pcal_end, rapt->mesg); + break; + case RECUR_WEEKLY: + date_sec2date_fmt (rapt->start, "%a", pcal_date); + fprintf (stream, "all %s on_or_after ", pcal_date); + date_sec2date_fmt (rapt->start, "%b %d", pcal_date); + fprintf (stream, "%s (%s -> %s) %s\n", pcal_date, pcal_beg, + pcal_end, rapt->mesg); + break; + case RECUR_MONTHLY: + date_sec2date_fmt (rapt->start, "%d", pcal_date); + fprintf (stream, "day on all %s (%s -> %s) %s\n", pcal_date, + pcal_beg, pcal_end, rapt->mesg); + break; + case RECUR_YEARLY: + date_sec2date_fmt (rapt->start, "%b %d", pcal_date); + fprintf (stream, "%s (%s -> %s) %s\n", pcal_date, pcal_beg, + pcal_end, rapt->mesg); + break; + default: + EXIT (_("incoherent repetition type")); + } + } + else + { + const long YEAR_START = calendar_start_of_year (); + const long YEAR_END = calendar_end_of_year (); + + if (rapt->start < YEAR_END && rapt->start > YEAR_START) + foreach_date_dump (YEAR_END, rapt->rpt, &rapt->exc, rapt->start, + rapt->dur, rapt->mesg, + (cb_dump_t)pcal_dump_apoint, stream); + } + } +} + +static void +pcal_export_apoints (FILE *stream) +{ + llist_item_t *i; + + fputs ("\n# ============\n# Appointments\n# ============\n", stream); + LLIST_TS_LOCK (&alist_p); + LLIST_TS_FOREACH (&alist_p, i) + { + struct apoint *apt = LLIST_TS_GET_DATA (i); + pcal_dump_apoint (stream, apt->start, apt->dur, apt->mesg); + } + LLIST_TS_UNLOCK (&alist_p); + fputc ('\n', stream); +} + +static void +pcal_export_todo (FILE *stream) +{ + llist_item_t *i; + + fputs ("#\n# Todos\n#\n", stream); + LLIST_FOREACH (&todolist, i) + { + struct todo *todo = LLIST_TS_GET_DATA (i); + if (todo->id < 0) /* completed items */ + continue; + + fputs ("note all ", stream); + fprintf (stream, "%d. %s\n", todo->id, todo->mesg); + } + fputc ('\n', stream); +} + +/* Export calcurse data. */ +void +pcal_export_data (FILE *stream) +{ + pcal_export_header (stream); + pcal_export_recur_events (stream); + pcal_export_events (stream); + pcal_export_recur_apoints (stream); + pcal_export_apoints (stream); + pcal_export_todo (stream); + pcal_export_footer (stream); +} + |