You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

473 lines
12 KiB

/*
* vcalical - An vcal/ical converter
* Copyright (C) 2006 Daniel Gollub <dgollub@suse.de>
* Copyright (C) 2006 Christopher Stender <cstender@suse.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "xml-support.h"
#include "vformat.h"
#include "xml-vcal.h"
#include <glib.h>
/* ical 2 vcal */
#define ATTR_COUNT (sizeof(rrule_attr)/sizeof(rrule_attr[0]))
#define PARAM_COUNT (sizeof(rrule_param)/sizeof(rrule_param[0]))
enum {
FIELD_FREQ,
FIELD_INTERVAL,
FIELD_FREQMOD,
FIELD_FREQMOD2,
FIELD_COUNTUNTIL,
NUM_OF_FIELDS
};
enum {
TYPE_ATTR,
TYPE_PARAM
};
struct _rrule_attr {
const char *ical;
const char *vcal;
int field;
} rrule_attr[] = {
{ "BYDAY", " ", FIELD_FREQMOD },
{ "BYMONTH", " ", FIELD_FREQMOD },
{ "BYMONTHDAY", " ", FIELD_FREQMOD },
{ "BYYEARDAY", " ", FIELD_FREQMOD },
{ "COUNT", " #", FIELD_COUNTUNTIL },
{ "FREQ", "", FIELD_FREQ },
{ "INTERVAL", "", FIELD_INTERVAL },
{ "UNTIL", " ", FIELD_COUNTUNTIL }
};
struct _rrule_param {
const char *ical;
const char *vcal;
} rrule_param[] = {
{ "DAILY", "D" },
{ "MONTHLY", "M" },
{ "WEEKLY", "W" },
{ "YEARLY", "YM" }
};
static int comp_attr(const void *m1, const void *m2) {
struct _rrule_attr *mi1 = (struct _rrule_attr *) m1;
struct _rrule_attr *mi2 = (struct _rrule_attr *) m2;
return strcmp(mi1->ical, mi2->ical);
}
static int comp_param(const void *m1, const void *m2) {
struct _rrule_param *mi1 = (struct _rrule_param *) m1;
struct _rrule_param *mi2 = (struct _rrule_param *) m2;
return strcmp(mi1->ical, mi2->ical);
}
struct _rrule_attr *_parse_rrule_attr(const char *ical) {
struct _rrule_attr key, *res;
key.ical = ical;
res = bsearch(&key, rrule_attr, ATTR_COUNT, sizeof(struct _rrule_attr), comp_attr);
if (!res)
return NULL;
return res;
}
const char *_parse_rrule_param(const char *ical) {
struct _rrule_param key, *res;
const char *ret = NULL;
key.ical = ical;
res = bsearch(&key, rrule_param, PARAM_COUNT, sizeof(struct _rrule_param), comp_param);
if (!res)
ret = ical;
else
ret = res->vcal;
return ret;
}
char *_blank_field(char *field) {
if (field)
g_free(field);
return g_strdup("");
}
char *_adapt_param(const char *param) {
int i, len;
GString *ret = g_string_new("");
len = strlen(param);
for (i=0; i < len; i++) {
switch(param[i]) {
// evil sperators like ','
case ',':
ret = g_string_append_c(ret, ' ');
break;
default:
ret = g_string_append_c(ret,param[i]);
}
}
return g_string_free(ret, FALSE);
}
void _vcal_hook(char **icalattrs, char **vcalattrs, char **icalparams, char **vcalparams) {
if (!strcmp(icalparams[FIELD_FREQ], "MONTHLY")) {
// Workround for RRULE:MP1 1+ SU 20071003T193000
if(!strcmp(icalattrs[FIELD_FREQMOD], "BYDAY")) {
char sign = '+';
char wday[3];
int nthday;
g_free(vcalparams[FIELD_FREQ]);
vcalparams[FIELD_FREQ] = g_strdup("MP");
g_free(vcalparams[FIELD_FREQMOD]);
if (strlen(icalparams[FIELD_FREQMOD]) > 3)
sscanf(icalparams[FIELD_FREQMOD], "%c%d%c%c", &sign, &nthday, &wday[0], &wday[1]);
else
sscanf(icalparams[FIELD_FREQMOD], "%d%c%c", &nthday, &wday[0], &wday[1]);
wday[2] = '\0';
vcalparams[FIELD_FREQMOD] = g_strdup_printf("%d%c %s", nthday, sign, wday);
// Workaround for RRULE:MD1 .......
// } else if (!strcmp(icalattrs[FIELD_FREQMOD], "BYMONTHDAY")) {
} else {
g_free(vcalparams[FIELD_FREQ]);
vcalparams[FIELD_FREQ] = g_strdup("MD");
}
}
if (!strcmp(icalparams[FIELD_FREQ], "YEARLY") && icalparams[FIELD_FREQMOD]) {
if (!strcmp(icalattrs[FIELD_FREQMOD], "BYYEARDAY")) {
g_free(vcalparams[FIELD_FREQ]);
vcalparams[FIELD_FREQ] = g_strdup("YD");
} else if ((!strcmp(icalattrs[FIELD_FREQMOD], "BYMONTH") && !strcmp(icalattrs[FIELD_FREQMOD2], "BYMONTHDAY"))
|| (!strcmp(icalattrs[FIELD_FREQMOD2], "BYMONTH") && !strcmp(icalattrs[FIELD_FREQMOD], "BYMONTHDAY"))) {
g_free(vcalparams[FIELD_FREQ]);
vcalparams[FIELD_FREQ] = g_strdup("YM");
vcalattrs[FIELD_FREQMOD] = _blank_field(vcalattrs[FIELD_FREQMOD]);
vcalattrs[FIELD_FREQMOD2] = _blank_field(vcalattrs[FIELD_FREQMOD2]);
vcalparams[FIELD_FREQMOD] = _blank_field(vcalparams[FIELD_FREQMOD]);
vcalparams[FIELD_FREQMOD2] = _blank_field(vcalparams[FIELD_FREQMOD2]);
}
}
// Set INTERVAL to 1 if nothing is set and BYMONTHDAY is not used
if (icalparams[FIELD_INTERVAL] == NULL) {
vcalparams[FIELD_INTERVAL] = g_strdup("1");
}
}
char *conv_ical2vcal_rrule(const char *ical) {
osync_trace(TRACE_ENTRY, "%s(%s)", __func__, ical);
int i;
const char *pos, *prev;
size_t len;
char *icalattrs[NUM_OF_FIELDS] = { NULL };
char *vcalattrs[NUM_OF_FIELDS] = { NULL };
char *icalparams[NUM_OF_FIELDS] = { NULL };
char *vcalparams[NUM_OF_FIELDS] = { NULL };
struct _rrule_attr *field_attr;
const char *tmp = NULL;
GString *vcal10 = g_string_new("");
pos = prev = ical;
// FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,FR;UNTIL=20060901T182200Z
// W1 TU FR 20060901T182200Z
// *FREQ**INTERVAL* *FREQ-MOD* *COUNT/UNTIL*
while ((pos = strstr(pos, "="))) {
GString *attr = g_string_new("");
GString *param = g_string_new("");
len = pos - prev;
// not equal is required ... ignoring =
for (i=0; i < len; i++)
attr = g_string_append_c(attr, prev[i]);
pos++;
prev = pos;
pos = strstr(pos, ";");
if (pos == NULL)
pos = ical + strlen(ical);
len = pos - prev;
for (i=0; i < len; i++)
param = g_string_append_c(param, prev[i]);
field_attr = _parse_rrule_attr(attr->str);
if (field_attr == NULL)
goto next;
if (icalattrs[field_attr->field] && field_attr->field == FIELD_FREQMOD)
field_attr->field += 1;
vcalattrs[field_attr->field] = g_strdup(field_attr->vcal);
icalattrs[field_attr->field] = g_strdup(attr->str);
tmp = _parse_rrule_param(param->str);
if (tmp)
vcalparams[field_attr->field] = _adapt_param(tmp);
else
vcalparams[field_attr->field] = g_strdup("");
icalparams[field_attr->field] = g_strdup(param->str);
g_string_free(attr, TRUE);
g_string_free(param, TRUE);
next:
prev = pos + 1;
}
for (i=0; i < NUM_OF_FIELDS; i++) {
if (!vcalparams[i])
vcalparams[i] = g_strdup("");
if (!vcalattrs[i])
vcalattrs[i] = g_strdup("");
if (!vcalparams[i])
vcalparams[i] = g_strdup("");
if (!icalattrs[i])
icalattrs[i] = g_strdup("");
}
_vcal_hook(icalattrs, vcalattrs, icalparams, vcalparams);
for (i=0; i < NUM_OF_FIELDS; i++) {
// If no end is set append #0 - recurrence for ever
if (i == FIELD_COUNTUNTIL && strlen(vcalparams[i]) == 0)
vcalparams[i] = g_strdup(" #0");
if (vcalattrs[i]) {
vcal10 = g_string_append(vcal10, vcalattrs[i]);
// printf("(%i) \"%s\"\n", i, vcalattrs[i]);
g_free(vcalattrs[i]);
}
if (vcalparams[i]) {
vcal10 = g_string_append(vcal10, vcalparams[i]);
// printf("(#%i) \"%s\"\n", i, vcalparams[i]);
g_free(vcalparams[i]);
}
if (icalattrs[i])
g_free(icalattrs[i]);
if (icalparams[i])
g_free(icalparams[i]);
}
osync_trace(TRACE_EXIT, "%s: %s", __func__, vcal10->str);
return g_string_free(vcal10, FALSE);
}
/* vcal 2 ical */
GList *conv_vcal2ical_rrule(const char *vcal) {
osync_trace(TRACE_ENTRY, "%s(%s)", __func__, vcal);
gchar** blocks = g_strsplit(vcal, " ", 256);
int offset = 0;
int frequency_state = 0;
char *frequency = NULL;
char *frequency_block = NULL;
int interval;
int duration_number = -1;
char *duration_block;
char* freq_mod = NULL;
/* count blocks */
int counter;
for(counter=0; blocks[counter]; counter++);
frequency_block = blocks[0];
duration_block = blocks[counter-1];
/* get frequency: only D(1), W(2), MP(3), MD(4), YD(5) and YM(6) is allowed */
switch (*frequency_block++) {
case 'D': frequency_state = 1; frequency = "DAILY"; break;
case 'W': frequency_state = 2; frequency = "WEEKLY"; break;
case 'M': frequency_state = 0;
switch (*frequency_block++) {
case 'P': frequency_state = 3; frequency = "MONTHLY"; break;
case 'D': frequency_state = 4; frequency = "MONTHLY"; break;
default:
osync_trace(TRACE_INTERNAL, "invalid frequency M<X>");
}
break;
case 'Y': frequency_state = 0;
switch (*frequency_block++) {
case 'D': frequency_state = 5; frequency = "YEARLY"; break;
case 'M': frequency_state = 6; frequency = "YEARLY"; break;
default:
osync_trace(TRACE_INTERNAL, "invalid frequency Y<X>");
}
break;
default:
osync_trace(TRACE_INTERNAL, "invalid or missing frequency");
}
/* get interval (integer) */
char* e;
interval = strtol(frequency_block, &e, 10);
if (e == frequency_block) {
osync_trace(TRACE_INTERNAL, "interval is missing.");
}
if (*e != 0) {
osync_trace(TRACE_INTERNAL, "interval is to long.");
}
/* get frequency modifier if there are more than two blocks */
if (counter > 2) {
GString *fm_buffer = g_string_new("");
int i;
/* for each modifier do... */
for(i=1; i < counter-1; i++) {
int count;
char sign;
if(fm_buffer->len > 0)
g_string_append(fm_buffer, ",");
/* check frequency modifier */
if (sscanf(blocks[i], "%d%c" , &count, &sign) == 2) {
/* we need to convert $COUNT- to -$COUNT -> RFC2445 */
if (sign == '-')
count = -count;
g_string_append_printf(fm_buffer, "%d", count);
if (i < counter-2 && !sscanf(blocks[i+1], "%d", &count)) {
g_string_append_printf(fm_buffer, " %s", blocks[i+1]);
i++;
}
} else {
/* e.g. Day or 'LD' (Last day) */
g_string_append(fm_buffer, blocks[i]);
}
}
freq_mod = fm_buffer->str;
g_string_free(fm_buffer, FALSE);
}
char *until = NULL;
/* get duration (number OR timestamp, but nothing is required) */
if (sscanf(duration_block, "#%d", &duration_number) < 1) {
if (!osync_time_isdate(duration_block)) {
/* Check if this duration_block is a localtime timestamp.
* If it is not UTC change the offset from 0 to the system UTC offset.
* vcal doesn't store any TZ information. This means the device have to be
* in the same Timezone as the host.
*/
if (!osync_time_isutc(duration_block)) {
struct tm *ttm = osync_time_vtime2tm(duration_block);
offset = osync_time_timezone_diff(ttm);
g_free(ttm);
}
until = osync_time_vtime2utc(duration_block, offset);
} else {
until = g_strdup(duration_block);
}
}
g_strfreev(blocks);
/* generate new RRULE: D(1), W(2), MP(3), MD(4), YD(5) and YM(6) */
GList *new_rrule = NULL;
new_rrule = g_list_append(new_rrule, g_strdup_printf("FREQ=%s", frequency));
new_rrule = g_list_append(new_rrule, g_strdup_printf("INTERVAL=%d", interval));
if (duration_number > 0)
new_rrule = g_list_append(new_rrule, g_strdup_printf("COUNT=%d", duration_number));
if(freq_mod != NULL) {
switch(frequency_state) {
case 2: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYDAY=%s", freq_mod)); break;
case 3: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYDAY=%s", freq_mod)); break;
case 4: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYMONTHDAY=%s", freq_mod)); break;
case 5: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYYEARDAY=%s", freq_mod)); break;
case 6: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYMONTH=%s", freq_mod)); break;
default:
break;
}
}
if (until != NULL) {
new_rrule = g_list_append(new_rrule, g_strdup_printf("UNTIL=%s", until));
g_free(until);
}
osync_trace(TRACE_EXIT, "%s", __func__);
return new_rrule;
}