commit a136a920536ec59d6a5ea43e6e6fd5935cb2f8f6 Author: bgraansm Date: Sat Aug 26 08:54:02 2017 -0400 first commit diff --git a/calmodule.c b/calmodule.c new file mode 100644 index 0000000..a37f4ea --- /dev/null +++ b/calmodule.c @@ -0,0 +1,199 @@ +/* calmodule.c + * Author: Bronson Graansma 0872249 + * Contact: bgraansm@mail.uoguelph.ca + * Date: March 17, 2016 + * Description: python - c extension for xcal */ + +#include +#include "calutil.h" +#include +#include + +static char* parseError(char* file, CalStatus status) { + char* err = "UNKNOWN"; + char* out = NULL; + + switch(status.code) { + case OK : err = "OK"; break; + case AFTEND: err = "AFTEND"; break; + case BADVER: err = "BADVER"; break; + case BEGEND: err = "BEGEND"; break; + case IOERR : err = "IOERR"; break; + case NOCAL : err = "NOCAL"; break; + case NOCRNL: err = "NOCRNL"; break; + case NODATA: err = "NODATA"; break; + case NOPROD: err = "NOPROD"; break; + case SUBCOM: err = "SUBCOM"; break; + case SYNTAX: err = "SYNTAX"; break; + } + + out = malloc(strlen(err) + strlen(file) + 30); + sprintf(out, "%s: %s in lines %d-%d\n", + file, err, status.linefrom, status.lineto); + + return out; +} + +static char* formatTime(char* str) { + //'YYYY-MM-DD HH:MM:SS' + char* format = malloc(20); + + assert(format != NULL); + if(str == NULL || strlen(str) < 15) { + free(format); + return "0000-00-00 00:00:00"; + } + + for(int i = 0; i < 19; i++) { + format[i] = ' '; + } + + for(int i = 0; i < 4; i++) { + format[i] = str[i]; + } + + for(int i = 0; i < 2; i++) { + format[i + 5] = str[i + 4]; + format[i + 8] = str[i + 6]; + format[i + 11] = str[i + 9]; + format[i + 14] = str[i + 11]; + format[i + 17] = str[i + 13]; + } + format[4] = '-'; + format[7] = '-'; + format[13] = ':'; + format[16] = ':'; + format[19] = '\0'; + + return format; +} + +static PyObject *Cal_readFile( PyObject *self, PyObject *args ) { + CalStatus status = initCalStatus(OK, 0,0); + char *filename = NULL; + PyObject *result = PyTuple_New(2); + PyObject *tuple = NULL; + FILE* ics = NULL; + CalComp* pcal = NULL; + + PyArg_ParseTuple(args, "s", &filename); + ics = fopen(filename, "r"); + + if(ics == NULL) { + PyTuple_SetItem(result, 0, Py_BuildValue("s", "error")); + PyTuple_SetItem(result, 1, Py_BuildValue("s", strerror(errno))); + return result; + } else { + status = readCalFile(ics, &pcal); + } + fclose(ics); + + if(status.code != OK) { + char* out = parseError(filename, status); + PyTuple_SetItem(result, 0, Py_BuildValue("s", "error")); + PyTuple_SetItem(result, 1, Py_BuildValue("s", out)); + free(out); + return result; + } + tuple = PyTuple_New(pcal->ncomps); + + for(int i = 0; i < pcal->ncomps; i++) { + char* summary = " "; + char* orgName = " "; + char* orgInfo = " "; + char* locaton = " "; + char* priorty = " "; + char* dtstart = "0000-00-00 00:00:00"; + CalProp* prop = pcal->comp[i]->prop; + PyObject *tmp = NULL; + + for(int j = 0; j < pcal->comp[i]->nprops; j++) { + if(strcmp(prop->name, "ORGANIZER") == 0) { + CalParam* param = prop->param; + for(int k = 0; k < prop->nparams; k++) { + if(strcmp(param->name, "CN") == 0) { + orgName = param->value[0]; + break; + } + param = param->next; + } + orgInfo = prop->value; + } else if(strcmp(prop->name, "DTSTART") == 0) { + dtstart = formatTime(prop->value); + } else if(strcmp(prop->name, "LOCATION") == 0) { + locaton = prop->value; + } else if(strcmp(prop->name, "PRIORITY") == 0) { + priorty = prop->value; + } else if(strcmp(prop->name, "SUMMARY") == 0) { + summary = prop->value; + } + prop = prop->next; + } + + tmp = Py_BuildValue("(s,i,i,s,s,s,s,s,s)",pcal->comp[i]->name, + pcal->comp[i]->nprops, pcal->comp[i]->ncomps, summary, + orgName, orgInfo, dtstart, locaton, priorty); + PyTuple_SetItem(tuple, i, tmp); + + if(strcmp(dtstart, "0000-00-00 00:00:00") != 0) { + free(dtstart); + } + } + PyTuple_SetItem(result, 0, Py_BuildValue("k", (unsigned long*)pcal)); + PyTuple_SetItem(result, 1, tuple); + + return result; +} + +static PyObject *Cal_writeFile(PyObject *self, PyObject *args ) { + CalStatus status = initCalStatus(OK, 0, 0); + char *filename = NULL; + CalComp *pcal = NULL; + FILE* ics = NULL; + int compNum = 0; + + PyArg_ParseTuple(args, "ski", &filename, (unsigned long*)&pcal, &compNum); + + ics = fopen(filename, "w"); + + if(ics == NULL) { + return Py_BuildValue("s", strerror(errno)); + } else if(compNum > -1) { + status = writeCalComp(ics, pcal->comp[compNum]); + } else { + status = writeCalComp(ics, pcal); + } + fclose(ics); + + if(status.code != OK) { + char* out = parseError(filename, status); + PyObject* pOut = Py_BuildValue("s", out); + free(out); + return pOut; + } + + return Py_BuildValue("s", "OK"); +} + +static PyObject *Cal_freeFile( PyObject *self, PyObject *args ) { + CalComp *pcal; + + PyArg_ParseTuple(args, "k", (unsigned long*)&pcal); + + freeCalComp(pcal); + + return Py_BuildValue(""); +} + +static PyMethodDef CalMethods[] = { + {"readFile", Cal_readFile, METH_VARARGS}, + {"writeFile", Cal_writeFile, METH_VARARGS}, + {"freeFile", Cal_freeFile, METH_VARARGS}, + {NULL, NULL} +}; + +static struct PyModuleDef calModuleDef = {PyModuleDef_HEAD_INIT, "Cal", NULL, -1, CalMethods}; + +PyMODINIT_FUNC PyInit_Cal(void) { + return PyModule_Create(&calModuleDef); +} diff --git a/caltool.c b/caltool.c new file mode 100644 index 0000000..b1516ee --- /dev/null +++ b/caltool.c @@ -0,0 +1,1028 @@ +/* caltool.c + * Author: Bronson Graansma 0872249 + * Contact: bgraansm@mail.uoguelph.ca + * Date: Feb 21, 2016 + * Description: Command line interface to interact with iCalandar + * ics files. */ + +#include "caltool.h" + +typedef struct CalEvent { + char* dtstart; + char* summary; + struct tm date; +} CalEvent; + +/* Function name: initCalComp + * Description: Initialize a CalComp's members to nul or 0 + * Args: The adress of the CalComp to initialize. */ +static void initCalComp(CalComp* comp) { + comp->name = NULL; + comp->prop = NULL; + comp->nprops = 0; + comp->ncomps = 0; +} + +/* Function name: parseError + * Description: Prints an error message to stderr saying the error in more + * readable terms, based on the status. + * Args: The name of the file that was parsed, and the status it received. */ +static void parseError(char* file, CalStatus status) { + char* err = "UNKNOWN"; + + switch(status.code) { + case OK : err = "OK"; break; + case AFTEND: err = "AFTEND"; break; + case BADVER: err = "BADVER"; break; + case BEGEND: err = "BEGEND"; break; + case IOERR : err = "IOERR"; break; + case NOCAL : err = "NOCAL"; break; + case NOCRNL: err = "NOCRNL"; break; + case NODATA: err = "NODATA"; break; + case NOPROD: err = "NOPROD"; break; + case SUBCOM: err = "SUBCOM"; break; + case SYNTAX: err = "SYNTAX"; break; + } + + fprintf(stderr, "%s: %s in lines %d-%d\n", + file, err, status.linefrom, status.lineto); +} + +/* Function name: freeCalEvent + * Description: Frees any allocated memory within a CalEvent. + * Args: The CalEvent to free the allocated memory in. */ +static void freeCalEvent(CalEvent event) { + free(event.dtstart); +} + +/* Function name: compareDates + * Description: Compare function used to qsort CalEvents by date. + * Args: Two void (CalEvent) pointers to compare. + * Returns: 1 if first > second, -1 if first < second, or 0 if equal. */ +static int compareDates(const void* date1, const void* date2) { + time_t time1 = mktime(&((CalEvent*)date1)->date); + time_t time2 = mktime(&((CalEvent*)date2)->date); + + return (time1 > time2) - (time1 < time2); +} + +/* Function name: compareStr + * Description: Compare function used to qsort strings alphabetically. + * Args: Two void (char*) pointers to compare. + * Returns: 1 if first > second, -1 if first < second, or 0 if equal. */ +static int compareStr(const void* str1, const void* str2) { + return strcmp(*(char**)str1, *(char**)str2); +} + +/* Function name: countComps + * Description: Counts top level V components. + * Args: The CalComp to sum up the V components. + * Returns: The number of top level V compenents. */ +static int countComps(const CalComp* comp) { + int comps = 0; + + for(int i = 0; i < comp->ncomps; i++) { + if(comp->comp[i]->name[0] == 'V') { + comps++; + } + } + return comps; +} + +/* Function name: countEvents + * Description: Counts how many VEVENTs are in a component. + * Args: The component to sum up the VEVENTs. + * Returns: The number of VEVENTs. */ +static int countEvents(const CalComp* comp) { + int events = 0; + + for(int i = 0; i < comp->ncomps; i++) { + if(strcmp(comp->comp[i]->name, "VEVENT") == 0) { + events++; + } + } + return events; +} + +/* Function name: countTodos + * Description: Counts how many VTODOs are in a component. + * Args: The component to sum up the VTODOs. + * Returns: The number of VTODOs. */ +static int countTodos(const CalComp* comp) { + int todos = 0; + + for(int i = 0; i < comp->ncomps; i++) { + if(strcmp(comp->comp[i]->name, "VTODO") == 0) { + todos++; + } + } + return todos; +} + +/* Function name: countSubcomps + * Description: Counts how many subcomponents are in a component. + * Args: The component to sum up the subcomponents. + * Returns: The number of subcomponents. */ +static int countSubcomps(const CalComp* comp) { + int subcomps = 0; + + if(comp == NULL) { + return 0; + } + + for(int i = 0; i < comp->ncomps; i++) { + subcomps += comp->comp[i]->ncomps + countSubcomps(comp->comp[i]); + } + + return subcomps; +} + +/* Function name: countOrganizers + * Description: Counts how many organizers are in a component. + * Stores the organizers CN value into orgList. + * Args: The component to sum up the organizers, the adress of the array + * of strings to store the organizers in, and the number of previously + * found organizers (call with zero, this is used for recursion). + * Returns: The number of organizers. */ +static int countOrganizers(const CalComp* comp, char*** orgList, int organs) { + for(int i = 0; i < comp->ncomps; i++) { + CalProp* prop = comp->comp[i]->prop; + + for(int j = 0; j < comp->comp[i]->nprops; j++) { + CalParam* param = prop->param; + + if(strcmp(prop->name, "ORGANIZER") == 0) { + for(int k = 0; k < prop->nparams; k++) { + if(strcmp(param->name, "CN") == 0) { + *orgList = realloc(*orgList, (organs + + param->nvalues + 1) * sizeof(char*)); + assert(*orgList != NULL); + for(int m = 0; m < param->nvalues; m++) { + (*orgList)[organs + m] = param->value[m]; + } // copy all the cn of organizers into a list + organs += param->nvalues; + } + param = param->next; + } + } + prop = prop->next; + } + organs = countOrganizers(comp->comp[i], orgList, organs); + } + return organs; +} + +/* Function name: countProps + * Description: Counts how many properties are in a component. + * Args: The component to sum up the properties. + * Returns: The number of properties. */ +static int countProps(const CalComp* comp) { + int props = 0; + + if(comp == NULL) { + return 0; + } + + props = comp->nprops; + for(int i = 0; i < comp->ncomps; i++) { + props += countProps(comp->comp[i]); + } + return props; +} + +/* Function name: isTimeProp + * Description: Checks if a property contains a time value. + * Args: The property to check if it's a time property. + * Returns: True if the property has a time, false otherwise. */ +static bool isTimeProp(CalProp* prop, bool mod) { + if(prop == NULL) { + return false; + } else if(strcmp(prop->name, "DTSTART" ) == 0 || + (strcmp(prop->name, "DTSTAMP") == 0 && mod) || + strcmp(prop->name, "DTEND" ) == 0 || + (strcmp(prop->name, "CREATED") == 0 && mod) || + strcmp(prop->name, "COMPLETED" ) == 0 || + strcmp(prop->name, "DUE" ) == 0 || + (strcmp(prop->name, "LAST-MODIFIED") == 0 && mod)) { + return true; + } + return false; +} + +/* Function name: formatTime + * Description: Creates a string that is easier to parse ("%Y %m %d %H %M %S") + * in strptime, than the property value. + * Args: The string to match a date with in an easier way. + * Expects str to be in format (yyyymmddThhmmss[Z]). + * Returns: The easier to parse date string. "%Y %m %d %H %M %S" */ +static char* formatTime(char* str) { + char* format = malloc(30); + + assert(format != NULL); + if(str == NULL || strlen(str) < 15) { + free(format); + return NULL; + } + + for(int i = 0; i < 19; i++) { + format[i] = ' '; + } + + for(int i = 0; i < 4; i++) { + format[i] = str[i]; + } + + //basically insert spaces between times for easy parsing + for(int i = 0; i < 2; i++) { + format[i + 5] = str[i + 4]; + format[i + 8] = str[i + 6]; + format[i + 11] = str[i + 9]; + format[i + 14] = str[i + 11]; + format[i + 17] = str[i + 13]; + } + format[19] = '\0'; + + return format; +} + +/* Function name: fetFirstDate + * Description: Fetches the earliest date from a component tree. + * Args: The component to retreive the oldest date from. + * Returns: The string of the date in format (yyyymmddThhmmss[Z]). */ +static char* getFirstDate(const CalComp* comp) { + char* dateStr = NULL; + char* dateTmp = NULL; + time_t lowest = 0; + time_t curent = 0; + struct tm dateTime; + + if(comp == NULL) { + return NULL; + } + + for(int i = 0; i < comp->ncomps; i++) { + CalProp* prop = comp->comp[i]->prop; + + if(strcmp(comp->comp[i]->name, "VTIMEZONE") == 0) { + continue; + } + + for(int j = 0; j < comp->comp[i]->nprops; j++) { + if(isTimeProp(prop, true) == true) { + dateTmp = formatTime(prop->value); + if(dateTmp == NULL) { + free(dateTmp); + continue; + } + strptime(dateTmp, "%Y %m %d %H %M %S",&dateTime); + dateTime.tm_isdst = -1; + curent = mktime(&dateTime); + + if(curent < lowest || lowest == 0) { + lowest = curent; + free(dateStr); + dateStr = dateTmp; + dateTmp = NULL; + } else { + free(dateTmp); + dateTmp = NULL; + } + } + prop = prop->next; + } + dateTmp = getFirstDate(comp->comp[i]); + if(dateTmp == NULL) { + free(dateTmp); + continue; + } + strptime(dateTmp, "%Y %m %d %H %M %S", &dateTime); + dateTime.tm_isdst = -1; + curent = mktime(&dateTime); + + if(curent < lowest || lowest == 0) { + lowest = curent; + free(dateStr); + dateStr = dateTmp; + } else { + free(dateTmp); + dateTmp = NULL; + } + } + + return dateStr; +} + +/* Function name: fetLastDate + * Description: Fetches the latest date from a component tree. + * Args: The component to retreive the newest date from. + * Returns: The string of the date in format (yyyymmddThhmmss[Z]). */ +static char* getLastDate(const CalComp* comp) { + char* dateStr = NULL; + char* dateTmp = NULL; + time_t highst = 0; + time_t curent = 0; + struct tm dateTime; + + if(comp == NULL) { + return NULL; + } + + for(int i = 0; i < comp->ncomps; i++) { + CalProp* prop = comp->comp[i]->prop; + + if(strcmp(comp->comp[i]->name, "VTIMEZONE") == 0) { + continue; + } + + for(int j = 0; j < comp->comp[i]->nprops; j++) { + if(isTimeProp(prop, true) == true) { + dateTmp = formatTime(prop->value); + if(dateTmp == NULL) { + free(dateTmp); + continue; + } + strptime(dateTmp, "%Y %m %d %H %M %S", &dateTime); + dateTime.tm_isdst = -1; + curent = mktime(&dateTime); + + if(curent > highst || highst == 0) { + highst = curent; + free(dateStr); + dateStr = dateTmp; + dateTmp = NULL; + } else { + free(dateTmp); + dateTmp = NULL; + } + } + prop = prop->next; + } + dateTmp = getLastDate(comp->comp[i]); + if(dateTmp == NULL) { + free(dateTmp); + continue; + } + strptime(dateTmp, "%Y %m %d %H %M %S", &dateTime); + dateTime.tm_isdst = -1; + curent = mktime(&dateTime); + + if(curent > highst || highst == 0) { + highst = curent; + free(dateStr); + dateStr = dateTmp; + } else { + free(dateTmp); + dateTmp = NULL; + } + } + return dateStr; +} + +/* Function name: isInRange + * Description: Checks of a component lies within two dates. If dates are + * unspecified, any time is good. + * Args: The component to check, and the two dates to see if the component + * is between them. + * Returns: True if the component falls within the dates, false otherwise. */ +static bool isInRange(const CalComp* comp, time_t datefrom, time_t dateto) { + CalProp* prop = comp->prop; + char* dateTmp = NULL; + time_t curent = 0; + struct tm date; + + if(comp == NULL) { + return false; + } + + for(int i = 0; i < comp->nprops; i++) { + if(isTimeProp(prop, false) == true) { + dateTmp = formatTime(prop->value); + if(dateTmp == NULL) { + free(dateTmp); + continue; + } + strptime(dateTmp, "%Y %m %d %H %M %S", &date); + date.tm_isdst = -1; + curent = mktime(&date); + free(dateTmp); + + if(datefrom == 0 && dateto == 0) { + return true; + } else if(datefrom == 0 && curent <= dateto) { + return true; + } else if(dateto == 0 && curent >= datefrom) { + return true; + } else if(curent >= datefrom && curent <= dateto) { + return true; + } else { + dateTmp = NULL; + } + } + for(int j = 0; j < comp->ncomps; j++) { + if(isInRange(comp->comp[j], datefrom, dateto) == true) { + return true; + } + } + prop = prop->next; + } + + return false; +} + +/* Function name: remDupe + * Description: Nullifies strings in a list that appear more than once. + * Args: The array of strings to remove duplicates from, and how many + * elements it has. + * Returns: How many non NULL elements remain in the list. */ +static int remDupe(char** list, int elements) { + int removed = 0; + + for(int i = 0; i < elements; i++) { + for(int j = i + 1; j < elements; j++) { + if(strcmp(list[i], list[j]) == 0) { + list[i] = NULL; + removed++; + break; + } + } + } + + return elements - removed; +} + +/* Function name: extract + * Description: Creates an array of strings that are all the X properties + * in a component, and stores how many elements are in the array + * in xprops. + * Args: The component to list the xproperties of, the adress of the array of + * strings to store their names in, and the adress of how many + * elements are being stored in the array. */ +static void extract(const CalComp* comp, char*** xprop, int* xprops) { + CalProp* prop = comp->prop; + + for(int i = 0; i < comp->nprops; i++) { + if(prop->name[0] == 'X' && prop->name[1] == '-') { + *xprop = realloc(*xprop, ((*xprops) + 1) * sizeof(char*)); + assert(*xprop != NULL); + (*xprop)[*xprops] = prop->name; + (*xprops)++; + } + prop = prop->next; + } + + for(int i = 0; i < comp->ncomps; i++) { + extract(comp->comp[i], xprop, xprops); + } +} + +CalStatus calInfo(const CalComp *comp, int lines, FILE *const txtfile) { + char** orgList = NULL; + char* first = getFirstDate(comp); + char* last = getLastDate(comp); + char* plural = ""; + char* from = "No dates"; + char* to = ""; + int components = countComps(comp); + int events = countEvents(comp); + int todos = countTodos(comp); + int others = (components - events) - todos; + int subcomps = countSubcomps(comp); + int organizers = countOrganizers(comp, &orgList, 0); + int props = countProps(comp); + int nonDupes = 0; + int linesWrote = 0; + struct tm date; + + if(first != NULL && last != NULL) { + strptime(first, "%Y %m %d %H %M %S", &date); + date.tm_isdst = -1; + strftime(first, 19, "%Y-%b-%d", &date); + strptime(last, "%Y %m %d %H %M %S", &date); + date.tm_isdst = -1; + strftime(last, 19, "%Y-%b-%d", &date); + } + + if(lines != 1) { + plural = "s"; + } + if(fprintf(txtfile, "%d line%s\n", lines, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + plural = ""; + + if(components != 1) { + plural = "s"; + } + if(fprintf(txtfile, "%d component%s: ", components, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + plural = ""; + + if(events != 1) { + plural = "s"; + } + if(fprintf(txtfile, "%d event%s, ", events, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + plural = ""; + + if(todos != 1) { + plural = "s"; + } + if(fprintf(txtfile, "%d todo%s, ", todos, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + plural = ""; + + if(others != 1) { + plural = "s"; + } + if(fprintf(txtfile, "%d other%s\n", others, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + plural = ""; + + if(subcomps != 1) { + plural = "s"; + } + if(fprintf(txtfile, "%d subcomponent%s\n", subcomps, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + plural = ""; + + if(props != 1) { + plural = "ies"; + } else { + plural = "y"; + } + if(fprintf(txtfile, "%d propert%s\n", props, plural) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + plural = ""; + + if(first != NULL) { + from = "From "; + to = " to "; + } else { + free(first); + free(last); + first = ""; + last = ""; + } + + if(fprintf(txtfile, "%s%s%s%s\n", from, first, to, last) < 0) { + free(first); + free(last); + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + if(strcmp(first, "") == 0) { + first = NULL; + last = NULL; + } + free(first); + free(last); + plural = ""; + + qsort(orgList, organizers, sizeof(char*), compareStr); + nonDupes = remDupe(orgList, organizers); + + if(nonDupes == 0) { + plural = "No o"; + } else { + plural = "O"; + } + + if(fprintf(txtfile, "%srganizers", plural) < 0) { + return initCalStatus(IOERR, linesWrote, linesWrote); + } + if(nonDupes != 0) { + if(fprintf(txtfile, ":") < 0) { + return initCalStatus(IOERR, linesWrote, linesWrote); + } + } + if(fprintf(txtfile, "\n") < 0) { + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + plural = ""; + + for(int i = 0; i < organizers; i++) { + if(orgList[i] == NULL) { + continue; + } + if(fprintf(txtfile, "%s\n", orgList[i]) < 0) { + return initCalStatus(IOERR, linesWrote, linesWrote); + } + linesWrote++; + } + free(orgList); + + return initCalStatus(OK, linesWrote, linesWrote); +} + +CalStatus calExtract(const CalComp *comp, CalOpt kind, FILE *const txtfile) { + CalError err = OK; + int linesWrote = 0; + + if(kind == OEVENT) { + CalEvent* event = NULL; + int events = 0; + + for(int i = 0; i < comp->ncomps; i++) { + CalProp* prop = comp->comp[i]->prop; + + if(strcmp(comp->comp[i]->name, "VEVENT") == 0) { + event = realloc(event, (events + 1) *sizeof(CalEvent)); + assert(event != NULL); + for(int j = 0; j < comp->comp[i]->nprops; j++) { + if(strcmp(prop->name, "DTSTART") == 0) { + event[events].dtstart = formatTime(prop->value); + strptime(event[events].dtstart, "%Y %m %d %H %M %S" + , &event[events].date); + event[events].date.tm_isdst = -1; + strftime(event[events].dtstart, 29, "%Y-%b-%d %l:%M %p" + , &event[events].date); + event[events].summary = NULL; + + prop = comp->comp[i]->prop; + for(int k = 0; k < comp->comp[i]->nprops; k++) { + if(strcmp(prop->name, "SUMMARY") == 0) { + event[events].summary = prop->value; + break; + } + prop = prop->next; + } + if(event[events].summary == NULL) { + event[events].summary = "(na)"; + } + break; + } + prop = prop->next; + } + events++; + } + } + qsort(event, events, sizeof(CalEvent), compareDates); + for(int i = 0; i < events; i++) { + if(fprintf(txtfile, "%s: %s\n", event[i].dtstart + , event[i].summary) < 0) { + err = IOERR; + break; + } + freeCalEvent(event[i]); + linesWrote++; + } + free(event); + } else if(kind == OPROP) { + char** xprop = NULL; + int xprops = 0; + + extract(comp, &xprop, &xprops); + qsort(xprop, xprops, sizeof(char*), compareStr); + remDupe(xprop, xprops); + + for(int i = 0; i < xprops; i++) { + if(xprop[i] == NULL) { + continue; + } + if(fprintf(txtfile, "%s\n", xprop[i]) < 0) { + err = IOERR; + break; + } + linesWrote++; + } + free(xprop); + } else { + err = IOERR; + } + + return initCalStatus(err, linesWrote, linesWrote); +} + +CalStatus calFilter(const CalComp *comp, CalOpt content, time_t datefrom, + time_t dateto, FILE *const icsfile) { + CalComp* copy = malloc(sizeof(CalComp)); + CalStatus status = initCalStatus(OK, 0, 0); + char* filter = ""; + + assert(copy != NULL); + initCalComp(copy); + copy->name = comp->name; + copy->nprops = comp->nprops; + copy->prop = comp->prop; + + if(content == OEVENT) { + filter = "VEVENT"; + } else if(content == OTODO) { + filter = "VTODO"; + } + + for(int i = 0; i < comp->ncomps; i++) { + if(strcmp(comp->comp[i]->name, filter) == 0) { + if(isInRange(comp->comp[i], datefrom, dateto) == true) { + copy = realloc(copy, sizeof(CalComp) + ((copy->ncomps + 1) + * sizeof(CalComp*))); + assert(copy != NULL); + copy->comp[copy->ncomps] = comp->comp[i]; + copy->ncomps++; + } + } + } + + if(copy->ncomps == 0) { + free(copy); + return initCalStatus(NOCAL, 0, 0); + } + + status = writeCalComp(icsfile, copy); + free(copy); + return status; +} + +CalStatus calCombine(const CalComp *comp1, const CalComp *comp2, + FILE *const icsfile) { + const size_t size = comp1->ncomps + comp2->ncomps; + CalComp* copy = malloc(sizeof(CalComp) + size * sizeof(CalComp*)); + CalStatus status = initCalStatus(OK, 0, 0); + CalProp* prop = comp2->prop; + + assert(copy != NULL); + + copy->name = comp1->name; + copy->prop = comp1->prop; + copy->nprops = comp1->nprops; + copy->ncomps = size; + + for(int i = 0; i < comp1->ncomps; i++) { + copy->comp[i] = comp1->comp[i]; + + } + for(int i = comp1->ncomps; i < size; i++) { + copy->comp[i] = comp2->comp[i - comp1->ncomps]; + } + for(int i = 0; i < comp2->nprops; i++) { + if(strcmp(prop->name, "VERSION") == 0); //don't copy + else if(strcmp(prop->name, "PRODID") == 0); //don't copy + else { + CalProp* tempProp = copy->prop; + + for(int j = 0; j < copy->nprops - 1; j++) { + tempProp = tempProp->next; + } + tempProp->next = prop; + copy->nprops++; + } + prop = prop->next; + } + + status = writeCalComp(icsfile, copy); + free(copy); + return status; +} + +/* Function name: main + * Description: Analyzes command line arguments and attempts to + * do as the user instructs. Reports feedback for + * errors, otherwise silently performs it's task. + * Args: Command line arguments. + * Returns: EXIT_SUCCESS if everything went nicely, otherwise EXIT_FAILURE. */ +int main(int argc, char** argv) { + CalStatus status = initCalStatus(OK, 0, 0); + CalOpt opt = OPROP; + FILE* ics = stdin; + FILE* ocs = stdout; + char* todayStr = NULL; + CalComp* comp = NULL; + int ioerr = 0; + time_t today = time(NULL); + struct tm todaym = *localtime(&today); + + if(argc < 2) { + fprintf(stderr, "general usage: %s