32 #include <libical/ical.h>
33 #include <ne_session.h>
35 #include <ne_request.h>
37 #include <ne_redirect.h>
46 static void *ical_load_calendar(
void *data);
47 static void *unref_icalendar(
void *obj);
52 .description =
"iCalendar .ics calendars",
53 .load_calendar = ical_load_calendar,
54 .unref_calendar = unref_icalendar,
70 static void icalendar_destructor(
void *obj)
74 ast_debug(1,
"Destroying pvt for iCalendar %s\n", pvt->owner->
name);
76 ne_session_destroy(pvt->session);
79 icalcomponent_free(pvt->data);
81 ne_uri_free(&pvt->uri);
89 static void *unref_icalendar(
void *obj)
97 static int fetch_response_reader(
void *data,
const char *block,
size_t len)
99 struct ast_str **response = data;
105 memcpy(tmp, block, len);
113 static int auth_credentials(
void *userdata,
const char *realm,
int attempts,
char *username,
char *secret)
118 ast_log(LOG_WARNING,
"Invalid username or password for iCalendar '%s'\n", pvt->owner->
name);
122 ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
123 ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);
128 static icalcomponent *fetch_icalendar(
struct icalendar_pvt *pvt)
133 icalcomponent *comp = NULL;
136 ast_log(LOG_ERROR,
"There is no private!\n");
141 ast_log(LOG_ERROR,
"Could not allocate memory for response.\n");
145 req = ne_request_create(pvt->session,
"GET", pvt->uri.path);
146 ne_add_response_body_reader(req, ne_accept_2xx, fetch_response_reader, &response);
148 ret = ne_request_dispatch(req);
149 ne_request_destroy(req);
151 ast_log(LOG_WARNING,
"Unable to retrieve iCalendar '%s' from '%s': %s\n", pvt->owner->
name, pvt->url, ne_get_error(pvt->session));
160 ast_log(LOG_WARNING,
"Failed to parse iCalendar data: %s\n", icalerror_perror());
168 static time_t icalfloat_to_timet(icaltimetype time)
174 tm.
tm_mon = time.month - 1;
194 static void icalendar_add_event(icalcomponent *comp,
struct icaltime_span *span,
void *data)
198 icaltimezone *utc = icaltimezone_get_utc_timezone();
200 icalcomponent *valarm;
202 struct icaltriggertype trigger;
204 if (!(pvt && pvt->owner)) {
205 ast_log(LOG_ERROR,
"Require a private structure with an owner\n");
210 ast_log(LOG_ERROR,
"Could not allocate an event!\n");
214 start = icalcomponent_get_dtstart(comp);
215 end = icalcomponent_get_dtend(comp);
217 event->start = icaltime_get_tzid(start) ? span->start : icalfloat_to_timet(start);
218 event->end = icaltime_get_tzid(end) ? span->end : icalfloat_to_timet(end);
219 event->busy_state = span->is_busy ? AST_CALENDAR_BS_BUSY : AST_CALENDAR_BS_FREE;
221 if ((prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY))) {
225 if ((prop = icalcomponent_get_first_property(comp, ICAL_DESCRIPTION_PROPERTY))) {
229 if ((prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY))) {
233 if ((prop = icalcomponent_get_first_property(comp, ICAL_LOCATION_PROPERTY))) {
237 if ((prop = icalcomponent_get_first_property(comp, ICAL_CATEGORIES_PROPERTY))) {
241 if ((prop = icalcomponent_get_first_property(comp, ICAL_PRIORITY_PROPERTY))) {
242 event->priority = icalvalue_get_integer(icalproperty_get_value(prop));
245 if ((prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY))) {
248 ast_log(LOG_WARNING,
"No UID found, but one is required. Generating, but updates may not be accurate\n");
249 if (!ast_strlen_zero(event->summary)) {
252 char tmp[AST_TIME_T_LEN];
265 if (icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY)
266 || icalcomponent_get_first_property(comp, ICAL_RDATE_PROPERTY)) {
267 icalcompiter comp_iter;
268 icaltimetype span_start = icaltime_from_timet_with_zone(
269 event->
start, icaltime_is_date(start), icaltime_get_timezone(start));
271 icaltime_set_timezone(&span_start, icaltime_get_timezone(start));
272 for (comp_iter = icalcomponent_begin_component(pvt->data, ICAL_VEVENT_COMPONENT);
273 icalcompiter_deref(&comp_iter);
274 icalcompiter_next(&comp_iter)) {
275 icalcomponent *vevent = icalcompiter_deref(&comp_iter);
276 icalproperty *uid = icalcomponent_get_first_property(vevent, ICAL_UID_PROPERTY);
278 if (uid && !strcmp(icalproperty_get_value_as_string(uid), event->uid)) {
279 icaltimetype recurrence_id = icalcomponent_get_recurrenceid(vevent);
282 icaltime_set_timezone(&recurrence_id, icaltime_get_timezone(start));
284 if (!icaltime_compare(recurrence_id, span_start)
285 && icaltime_is_date(span_start) == icaltime_is_date(recurrence_id)) {
294 for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
295 prop; prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
299 if (!(attendee =
ast_calloc(1,
sizeof(*attendee)))) {
303 data = icalproperty_get_attendee(prop);
304 if (ast_strlen_zero(data)) {
316 if (!(valarm = icalcomponent_get_first_component(comp, ICAL_VALARM_COMPONENT))) {
322 if (!(prop = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY))) {
323 ast_log(LOG_WARNING,
"VALARM has no TRIGGER, skipping!\n");
329 trigger = icalproperty_get_trigger(prop);
331 if (icaltriggertype_is_null_trigger(trigger)) {
332 ast_log(LOG_WARNING,
"Bad TRIGGER for VALARM, skipping!\n");
338 if (!icaltime_is_null_time(trigger.time)) {
339 tmp = icaltime_convert_to_zone(trigger.time, utc);
340 event->alarm = icaltime_as_timet_with_zone(tmp, utc);
344 tmp = icaltime_add(start, trigger.duration);
345 event->alarm = icaltime_as_timet_with_zone(tmp, icaltime_get_timezone(start));
354 static void icalendar_update_events(
struct icalendar_pvt *pvt)
356 struct icaltimetype start_time, end_time;
360 ast_log(LOG_ERROR,
"iCalendar is NULL\n");
365 ast_log(LOG_ERROR,
"iCalendar is an orphan!\n");
370 ast_log(LOG_ERROR,
"The iCalendar has not been parsed!\n");
374 start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
375 end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
376 end_time.second += pvt->owner->
timeframe * 60;
377 end_time = icaltime_normalize(end_time);
379 for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT);
381 iter = icalcomponent_get_next_component(pvt->data, ICAL_VEVENT_COMPONENT))
383 icalcomponent_foreach_recurrence(iter, start_time, end_time, icalendar_add_event, pvt);
389 static void *ical_load_calendar(
void *void_data)
398 ast_log(LOG_ERROR,
"You must enable calendar support for res_icalendar to load\n");
401 if (ao2_trylock(cal)) {
402 if (cal->unloading) {
403 ast_log(LOG_WARNING,
"Unloading module, load_calendar cancelled.\n");
405 ast_log(LOG_WARNING,
"Could not lock calendar, aborting!\n");
411 if (!(pvt = ao2_alloc(
sizeof(*pvt), icalendar_destructor))) {
412 ast_log(LOG_ERROR,
"Could not allocate icalendar_pvt structure for calendar: %s\n", cal->
name);
420 ast_log(LOG_ERROR,
"Could not allocate space for fetching events for calendar: %s\n", cal->
name);
421 pvt = unref_icalendar(pvt);
428 ast_log(LOG_ERROR,
"Couldn't allocate string field space for calendar: %s\n", cal->
name);
429 pvt = unref_icalendar(pvt);
435 for (v = ast_variable_browse(cfg, cal->
name); v; v = v->
next) {
436 if (!strcasecmp(v->
name,
"url")) {
438 }
else if (!strcasecmp(v->
name,
"user")) {
440 }
else if (!strcasecmp(v->
name,
"secret")) {
447 if (ast_strlen_zero(pvt->url)) {
448 ast_log(LOG_WARNING,
"No URL was specified for iCalendar '%s' - skipping.\n", cal->
name);
449 pvt = unref_icalendar(pvt);
454 if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) {
455 ast_log(LOG_WARNING,
"Could not parse url '%s' for iCalendar '%s' - skipping.\n", pvt->url, cal->
name);
456 pvt = unref_icalendar(pvt);
461 if (pvt->uri.scheme == NULL) {
462 pvt->uri.scheme =
"http";
465 if (pvt->uri.port == 0) {
466 pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
469 pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
470 ne_redirect_register(pvt->session);
471 ne_set_server_auth(pvt->session, auth_credentials, pvt);
472 ne_set_useragent(pvt->session,
"Asterisk");
473 if (!strcasecmp(pvt->uri.scheme,
"https")) {
474 ne_ssl_trust_default_ca(pvt->session);
479 ast_mutex_init(&refreshlock);
482 if (!(pvt->data = fetch_icalendar(pvt))) {
483 ast_log(LOG_WARNING,
"Unable to parse iCalendar '%s'\n", cal->
name);
486 icalendar_update_events(pvt);
493 struct timespec ts = {0,};
495 ts.tv_sec = tv.tv_sec + (60 * pvt->owner->
refresh);
497 ast_mutex_lock(&refreshlock);
498 while (!pvt->owner->unloading) {
499 if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
503 ast_mutex_unlock(&refreshlock);
505 if (pvt->owner->unloading) {
506 ast_debug(10,
"Skipping refresh since we got a shutdown signal\n");
514 icalcomponent_free(pvt->data);
517 if (!(pvt->data = fetch_icalendar(pvt))) {
518 ast_log(LOG_WARNING,
"Unable to parse iCalendar '%s'\n", pvt->owner->
name);
522 icalendar_update_events(pvt);
528 static int load_module(
void)
539 static int unload_module(
void)
546 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER,
"Asterisk iCalendar .ics file integration",
547 .support_level = AST_MODULE_SUPPORT_EXTENDED,
549 .unload = unload_module,
551 .requires =
"res_calendar",
struct ast_variable * next
Asterisk locking-related definitions:
int ast_calendar_register(struct ast_calendar_tech *tech)
Register a new calendar technology.
Asterisk main include file. File version handling, generic pbx functions.
int ast_time_t_to_string(time_t time, char *buf, size_t length)
Converts to a string representation of a time_t as decimal seconds since the epoch. Returns -1 on failure, zero otherwise.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container, as described below.
Structure for variables, used for configurations and for channel variables.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
#define ast_strdup(str)
A wrapper for strdup()
void ast_calendar_merge_events(struct ast_calendar *cal, struct ao2_container *new_events)
Add an event to the list of events for a calendar.
Configuration File Parser.
General Asterisk PBX channel definitions.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
struct ao2_container * ast_calendar_event_container_alloc(void)
Allocate an astobj2 container for ast_calendar_event objects.
#define AST_STRING_FIELD(name)
Declare a string field.
A general API for managing calendar events with Asterisk.
static struct stasis_rest_handlers events
REST handler for /api-docs/events.json.
#define ast_malloc(len)
A wrapper for malloc()
#define ast_debug(level,...)
Log a DEBUG message.
const ast_string_field name
struct ast_calendar_event * ast_calendar_event_alloc(struct ast_calendar *cal)
Allocate an astobj2 ast_calendar_event object.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Support for dynamic strings.
#define ast_calloc(num, len)
A wrapper for calloc()
const struct ast_config * ast_calendar_config_acquire(void)
Grab and lock pointer to the calendar config (read only)
Module has failed to load, may be in an inconsistent state.
struct ast_calendar_event * ast_calendar_unref_event(struct ast_calendar_event *event)
Unreference an ast_calendar_event.
structure to hold users read from users.conf
void ast_calendar_unregister(struct ast_calendar_tech *tech)
Unregister a new calendar technology.
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
struct timeval ast_mktime(struct ast_tm *const tmp, const char *zone)
Timezone-independent version of mktime(3).
Individual calendaring technology data.
Asterisk calendar structure.
void ast_calendar_config_release(void)
Release the calendar config.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Structure for mutex and tracking information.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
#define ao2_link(container, obj)
Add an object to a container.