Packages

NIF bindings to libical providing icalendar rrule expansion.

Current section

Files

Jump to
excal src recurrence iterator.c
Raw

src/recurrence/iterator.c

#include <stdio.h>
#include <string.h>
#include "libical/ical.h"
#include "erl_nif.h"
typedef struct
{
icalrecur_iterator *iterator;
} IteratorResource;
ErlNifResourceType *EXCAL_RECUR_ITERATOR_RES_TYPE;
void recurrence_iterator_free(ErlNifEnv *env, void *res)
{
icalrecur_iterator *iterator = ((IteratorResource *)res)->iterator;
icalrecur_iterator_free(iterator);
}
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
EXCAL_RECUR_ITERATOR_RES_TYPE = enif_open_resource_type(env, NULL, "IteratorResource", recurrence_iterator_free, flags, NULL);
return 0;
}
int upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info)
{
return 0;
}
static ERL_NIF_TERM
icaltime_to_erl_datetime(ErlNifEnv *env, struct icaltimetype datetime)
{
return enif_make_tuple2(
env,
enif_make_tuple3(
env,
enif_make_int(env, datetime.year),
enif_make_int(env, datetime.month),
enif_make_int(env, datetime.day)),
enif_make_tuple3(
env,
enif_make_int(env, datetime.hour),
enif_make_int(env, datetime.minute),
enif_make_int(env, datetime.second)));
}
static ERL_NIF_TERM
icaltime_to_erl_date(ErlNifEnv *env, struct icaltimetype date)
{
return enif_make_tuple3(
env,
enif_make_int(env, date.year),
enif_make_int(env, date.month),
enif_make_int(env, date.day));
}
static ERL_NIF_TERM
make_error_tuple(ErlNifEnv *env, const char *error)
{
return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_atom(env, error));
}
static ERL_NIF_TERM
make_ok_tuple(ErlNifEnv *env, ERL_NIF_TERM output)
{
return enif_make_tuple2(env, enif_make_atom(env, "ok"), output);
}
static ERL_NIF_TERM
make_nil(ErlNifEnv *env)
{
return enif_make_atom(env, "nil");
}
int get_iterator(ErlNifEnv *env, ERL_NIF_TERM arg, icalrecur_iterator **iterator)
{
// read arg as iterator resource
IteratorResource *iterator_resource;
if (!enif_get_resource(env, arg, EXCAL_RECUR_ITERATOR_RES_TYPE, (void **)&iterator_resource))
{
return 0;
}
// get the ical iterator from the resource struct
(*iterator) = iterator_resource->iterator;
return 1;
}
int get_string_from_binary(ErlNifEnv *env, ERL_NIF_TERM arg, char **output)
{
// read arg as binary
ErlNifBinary binary;
if (!enif_inspect_iolist_as_binary(env, arg, &binary))
{
// argument was not a binary
return 0;
}
// copy binary to local string
(*output) = strndup((char *)binary.data, binary.size);
return 1;
}
static ERL_NIF_TERM
recurrence_iterator_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
char *rrule_string;
if (argc < 1 || !get_string_from_binary(env, argv[0], &rrule_string))
{
return enif_make_badarg(env);
}
char *dtstart_string;
if (argc < 2 || !get_string_from_binary(env, argv[1], &dtstart_string))
{
return enif_make_badarg(env);
}
// make the start time struct from the dtstart string
// TODO: timezones?
struct icaltimetype dtstart = icaltime_from_string(dtstart_string);
if (icaltime_is_null_time(dtstart))
{
return make_error_tuple(env, "invalid_dtstart");
}
// make the recurrence struct from the rrule string
icalerror_set_errno(ICAL_NO_ERROR); // reset error first
struct icalrecurrencetype recur = icalrecurrencetype_from_string(rrule_string);
if (icalerrno == ICAL_MALFORMEDDATA_ERROR || icalerrno == ICAL_NEWFAILED_ERROR)
{
return make_error_tuple(env, "invalid_rrule");
}
// initialize the iterator
icalrecur_iterator *iterator = icalrecur_iterator_new(recur, dtstart);
if (iterator == 0)
{
// not sure if this is even possible
return make_error_tuple(env, "bad_iterator");
}
// wrap the iterator in a resource struct
IteratorResource *iterator_resource = enif_alloc_resource(EXCAL_RECUR_ITERATOR_RES_TYPE, sizeof(IteratorResource));
iterator_resource->iterator = iterator;
// convert to erlang resource term and release to erlang memory management
ERL_NIF_TERM iterator_term = enif_make_resource(env, iterator_resource);
enif_release_resource(iterator_resource);
// return {:ok, iterator}
return make_ok_tuple(env, iterator_term);
}
static ERL_NIF_TERM
recurrence_iterator_set_start(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
// get iterator arg
icalrecur_iterator *iterator;
if (argc < 1 || !get_iterator(env, argv[0], &iterator))
{
return enif_make_badarg(env);
}
// get start string arg
char *start_string;
if (argc < 2 || !get_string_from_binary(env, argv[1], &start_string))
{
return enif_make_badarg(env);
}
// build start from start_string
// TODO: timezones?
struct icaltimetype start = icaltime_from_string(start_string);
if (icaltime_is_null_time(start))
{
return make_error_tuple(env, "invalid_start");
}
// set iterator start
if (icalrecur_iterator_set_start(iterator, start))
{
// return :ok
return enif_make_atom(env, "ok");
}
else
{
// you can't set start on rules that use COUNT
// return {:error, :invalid_start}
return make_error_tuple(env, "start_invalid_for_rule");
}
}
static ERL_NIF_TERM
recurrence_iterator_set_end(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
// get iterator arg
icalrecur_iterator *iterator;
if (argc < 1 || !get_iterator(env, argv[0], &iterator))
{
return enif_make_badarg(env);
}
// get end string arg
char *end_string;
if (argc < 2 || !get_string_from_binary(env, argv[1], &end_string))
{
return enif_make_badarg(env);
}
// build end from end_string
// TODO: timezones?
struct icaltimetype end = icaltime_from_string(end_string);
if (icaltime_is_null_time(end))
{
return make_error_tuple(env, "invalid_end");
}
// set iterator end
icalrecur_iterator_set_end(iterator, end);
// return :ok
return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM
recurrence_iterator_next(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
// get iterator arg
icalrecur_iterator *iterator;
if (argc < 1 || !get_iterator(env, argv[0], &iterator))
{
return enif_make_badarg(env);
}
// get next occurrence
icaltimetype occurrence = icalrecur_iterator_next(iterator);
// make response, either nil, a date, or a datetime
ERL_NIF_TERM occurrence_term;
if (icaltime_is_null_time(occurrence))
{
occurrence_term = make_nil(env);
}
else if (occurrence.is_date)
{
occurrence_term = icaltime_to_erl_date(env, occurrence);
}
else
{
occurrence_term = icaltime_to_erl_datetime(env, occurrence);
}
// return occurrence
return occurrence_term;
}
static ErlNifFunc nif_funcs[] = {
{"new", 2, recurrence_iterator_new},
{"set_start", 2, recurrence_iterator_set_start},
{"set_end", 2, recurrence_iterator_set_end},
{"next", 1, recurrence_iterator_next}};
ERL_NIF_INIT(Elixir.Excal.Interface.Recurrence.Iterator, nif_funcs, &load, NULL, &upgrade, NULL)