Sat Apr 26 2014 22:03:08

Asterisk developer's documentation


res_calendar_icalendar.c File Reference

Resource for handling iCalendar calendars. More...

#include "asterisk.h"
#include <libical/ical.h>
#include <ne_session.h>
#include <ne_uri.h>
#include <ne_request.h>
#include <ne_auth.h>
#include <ne_redirect.h>
#include "asterisk/module.h"
#include "asterisk/calendar.h"
#include "asterisk/lock.h"
#include "asterisk/config.h"
#include "asterisk/astobj2.h"
Include dependency graph for res_calendar_icalendar.c:

Go to the source code of this file.

Data Structures

struct  icalendar_pvt

Functions

static void __reg_module (void)
static void __unreg_module (void)
static int auth_credentials (void *userdata, const char *realm, int attempts, char *username, char *secret)
static icalcomponent * fetch_icalendar (struct icalendar_pvt *pvt)
static int fetch_response_reader (void *data, const char *block, size_t len)
static void * ical_load_calendar (void *data)
static void icalendar_add_event (icalcomponent *comp, struct icaltime_span *span, void *data)
static void icalendar_destructor (void *obj)
static void icalendar_update_events (struct icalendar_pvt *pvt)
static time_t icalfloat_to_timet (icaltimetype time)
static int load_module (void)
static int unload_module (void)
static void * unref_icalendar (void *obj)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Asterisk iCalendar .ics file integration" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEVSTATE_PLUGIN, }
static struct ast_module_infoast_module_info = &__mod_info
static struct ast_calendar_tech ical_tech

Detailed Description

Resource for handling iCalendar calendars.

Definition in file res_calendar_icalendar.c.


Function Documentation

static void __reg_module ( void  ) [static]

Definition at line 509 of file res_calendar_icalendar.c.

static void __unreg_module ( void  ) [static]

Definition at line 509 of file res_calendar_icalendar.c.

static int auth_credentials ( void *  userdata,
const char *  realm,
int  attempts,
char *  username,
char *  secret 
) [static]

Definition at line 112 of file res_calendar_icalendar.c.

References ast_log(), LOG_WARNING, ast_calendar::name, icalendar_pvt::owner, icalendar_pvt::secret, and icalendar_pvt::user.

Referenced by ical_load_calendar().

{
   struct icalendar_pvt *pvt = userdata;

   if (attempts > 1) {
      ast_log(LOG_WARNING, "Invalid username or password for iCalendar '%s'\n", pvt->owner->name);
      return -1;
   }

   ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
   ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);

   return 0;
}
static icalcomponent* fetch_icalendar ( struct icalendar_pvt pvt) [static]

Definition at line 127 of file res_calendar_icalendar.c.

References ast_free, ast_log(), ast_str_buffer(), ast_str_create(), ast_str_strlen(), ast_strlen_zero(), fetch_response_reader(), LOG_ERROR, LOG_WARNING, ast_calendar::name, icalendar_pvt::owner, icalendar_pvt::session, icalendar_pvt::uri, and icalendar_pvt::url.

Referenced by ical_load_calendar().

{
   int ret;
   struct ast_str *response;
   ne_request *req;
   icalcomponent *comp = NULL;

   if (!pvt) {
      ast_log(LOG_ERROR, "There is no private!\n");
      return NULL;
   }

   if (!(response = ast_str_create(512))) {
      ast_log(LOG_ERROR, "Could not allocate memory for response.\n");
      return NULL;
   }

   req = ne_request_create(pvt->session, "GET", pvt->uri.path);
   ne_add_response_body_reader(req, ne_accept_2xx, fetch_response_reader, &response);

   ret = ne_request_dispatch(req);
   ne_request_destroy(req);
   if (ret != NE_OK || !ast_str_strlen(response)) {
      ast_log(LOG_WARNING, "Unable to retrieve iCalendar '%s' from '%s': %s\n", pvt->owner->name, pvt->url, ne_get_error(pvt->session));
      ast_free(response);
      return NULL;
   }

   if (!ast_strlen_zero(ast_str_buffer(response))) {
      comp = icalparser_parse_string(ast_str_buffer(response));
   }
   ast_free(response);

   return comp;
}
static int fetch_response_reader ( void *  data,
const char *  block,
size_t  len 
) [static]

Definition at line 96 of file res_calendar_icalendar.c.

References ast_free, ast_malloc, ast_str_append(), and len().

Referenced by fetch_icalendar().

{
   struct ast_str **response = data;
   unsigned char *tmp;

   if (!(tmp = ast_malloc(len + 1))) {
      return -1;
   }
   memcpy(tmp, block, len);
   tmp[len] = '\0';
   ast_str_append(response, 0, "%s", tmp);
   ast_free(tmp);

   return 0;
}
static void * ical_load_calendar ( void *  data) [static]

Definition at line 349 of file res_calendar_icalendar.c.

References ao2_alloc, ao2_trylock, ao2_unlock, ast_calendar_config_acquire(), ast_calendar_config_release(), ast_calendar_event_container_alloc(), ast_cond_timedwait, ast_debug, ast_log(), ast_mutex_init, ast_mutex_lock, ast_mutex_unlock, ast_string_field_init, ast_string_field_set, ast_strlen_zero(), ast_tvnow(), ast_variable_browse(), auth_credentials(), icalendar_pvt::data, icalendar_pvt::events, fetch_icalendar(), icalendar_destructor(), icalendar_update_events(), LOG_ERROR, LOG_WARNING, ast_variable::name, ast_calendar::name, ast_variable::next, icalendar_pvt::owner, ast_calendar::refresh, refreshlock, secret, icalendar_pvt::session, ast_calendar::tech_pvt, ast_calendar::unload, ast_calendar::unloading, unref_icalendar(), icalendar_pvt::uri, icalendar_pvt::url, url, and ast_variable::value.

{
   struct icalendar_pvt *pvt;
   const struct ast_config *cfg;
   struct ast_variable *v;
   struct ast_calendar *cal = void_data;
   ast_mutex_t refreshlock;

   if (!(cal && (cfg = ast_calendar_config_acquire()))) {
      ast_log(LOG_ERROR, "You must enable calendar support for res_icalendar to load\n");
      return NULL;
   }
   if (ao2_trylock(cal)) {
      if (cal->unloading) {
         ast_log(LOG_WARNING, "Unloading module, load_calendar cancelled.\n");
      } else {
         ast_log(LOG_WARNING, "Could not lock calendar, aborting!\n");
      }
      ast_calendar_config_release();
      return NULL;
   }

   if (!(pvt = ao2_alloc(sizeof(*pvt), icalendar_destructor))) {
      ast_log(LOG_ERROR, "Could not allocate icalendar_pvt structure for calendar: %s\n", cal->name);
      ast_calendar_config_release();
      return NULL;
   }

   pvt->owner = cal;

   if (!(pvt->events = ast_calendar_event_container_alloc())) {
      ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name);
      pvt = unref_icalendar(pvt);
      ao2_unlock(cal);
      ast_calendar_config_release();
      return NULL;
   }

   if (ast_string_field_init(pvt, 32)) {
      ast_log(LOG_ERROR, "Couldn't allocate string field space for calendar: %s\n", cal->name);
      pvt = unref_icalendar(pvt);
      ao2_unlock(cal);
      ast_calendar_config_release();
      return NULL;
   }

   for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) {
      if (!strcasecmp(v->name, "url")) {
         ast_string_field_set(pvt, url, v->value);
      } else if (!strcasecmp(v->name, "user")) {
         ast_string_field_set(pvt, user, v->value);
      } else if (!strcasecmp(v->name, "secret")) {
         ast_string_field_set(pvt, secret, v->value);
      }
   }

   ast_calendar_config_release();

   if (ast_strlen_zero(pvt->url)) {
      ast_log(LOG_WARNING, "No URL was specified for iCalendar '%s' - skipping.\n", cal->name);
      pvt = unref_icalendar(pvt);
      ao2_unlock(cal);
      return NULL;
   }

   if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) {
      ast_log(LOG_WARNING, "Could not parse url '%s' for iCalendar '%s' - skipping.\n", pvt->url, cal->name);
      pvt = unref_icalendar(pvt);
      ao2_unlock(cal);
      return NULL;
   }

   if (pvt->uri.scheme == NULL) {
      pvt->uri.scheme = "http";
   }

   if (pvt->uri.port == 0) {
      pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
   }

   pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
   ne_redirect_register(pvt->session);
   ne_set_server_auth(pvt->session, auth_credentials, pvt);
   if (!strcasecmp(pvt->uri.scheme, "https")) {
      ne_ssl_trust_default_ca(pvt->session);
   }

   cal->tech_pvt = pvt;

   ast_mutex_init(&refreshlock);

   /* Load it the first time */
   if (!(pvt->data = fetch_icalendar(pvt))) {
      ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", cal->name);
   }

   icalendar_update_events(pvt);

   ao2_unlock(cal);

   /* The only writing from another thread will be if unload is true */
   for(;;) {
      struct timeval tv = ast_tvnow();
      struct timespec ts = {0,};

      ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh);

      ast_mutex_lock(&refreshlock);
      while (!pvt->owner->unloading) {
         if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
            break;
         }
      }
      ast_mutex_unlock(&refreshlock);

      if (pvt->owner->unloading) {
         ast_debug(10, "Skipping refresh since we got a shutdown signal\n");
         return NULL;
      }

      ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh);

      /* Free the old calendar data */
      if (pvt->data) {
         icalcomponent_free(pvt->data);
         pvt->data = NULL;
      }
      if (!(pvt->data = fetch_icalendar(pvt))) {
         ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", pvt->owner->name);
         continue;
      }

      icalendar_update_events(pvt);
   }

   return NULL;
}
static void icalendar_add_event ( icalcomponent *  comp,
struct icaltime_span *  span,
void *  data 
) [static]

Definition at line 189 of file res_calendar_icalendar.c.

References ao2_link, AST_CALENDAR_BS_BUSY, AST_CALENDAR_BS_FREE, ast_calendar_event_alloc(), ast_calendar_unref_event(), ast_calloc, ast_free, AST_LIST_INSERT_TAIL, ast_log(), ast_strdup, ast_string_field_set, ast_strlen_zero(), ast_calendar_event::attendees, icalendar_pvt::data, ast_calendar_attendee::data, ast_calendar_event::end, icalendar_pvt::events, icalfloat_to_timet(), LOG_ERROR, LOG_WARNING, ast_calendar_attendee::next, icalendar_pvt::owner, ast_calendar_event::start, and ast_calendar_event::summary.

Referenced by icalendar_update_events().

{
   struct icalendar_pvt *pvt = data;
   struct ast_calendar_event *event;
   icaltimezone *utc = icaltimezone_get_utc_timezone();
   icaltimetype start, end, tmp;
   icalcomponent *valarm;
   icalproperty *prop;
   struct icaltriggertype trigger;

   if (!(pvt && pvt->owner)) {
      ast_log(LOG_ERROR, "Require a private structure with an ownenr\n");
      return;
   }

   if (!(event = ast_calendar_event_alloc(pvt->owner))) {
      ast_log(LOG_ERROR, "Could not allocate an event!\n");
      return;
   }

   start = icalcomponent_get_dtstart(comp);
   end = icalcomponent_get_dtend(comp);

   event->start = icaltime_get_tzid(start) ? span->start : icalfloat_to_timet(start);
   event->end = icaltime_get_tzid(end) ? span->end : icalfloat_to_timet(end);
   event->busy_state = span->is_busy ? AST_CALENDAR_BS_BUSY : AST_CALENDAR_BS_FREE;

   if ((prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY))) {
      ast_string_field_set(event, summary, icalproperty_get_value_as_string(prop));
   }

   if ((prop = icalcomponent_get_first_property(comp, ICAL_DESCRIPTION_PROPERTY))) {
      ast_string_field_set(event, description, icalproperty_get_value_as_string(prop));
   }

   if ((prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY))) {
      ast_string_field_set(event, organizer, icalproperty_get_value_as_string(prop));
   }

   if ((prop = icalcomponent_get_first_property(comp, ICAL_LOCATION_PROPERTY))) {
      ast_string_field_set(event, location, icalproperty_get_value_as_string(prop));
   }

   if ((prop = icalcomponent_get_first_property(comp, ICAL_CATEGORIES_PROPERTY))) {
      ast_string_field_set(event, categories, icalproperty_get_value_as_string(prop));
   }

   if ((prop = icalcomponent_get_first_property(comp, ICAL_PRIORITY_PROPERTY))) {
      event->priority = icalvalue_get_integer(icalproperty_get_value(prop));
   }

   if ((prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY))) {
      ast_string_field_set(event, uid, icalproperty_get_value_as_string(prop));
   } else {
      ast_log(LOG_WARNING, "No UID found, but one is required. Generating, but updates may not be acurate\n");
      if (!ast_strlen_zero(event->summary)) {
         ast_string_field_set(event, uid, event->summary);
      } else {
         char tmp[100];
         snprintf(tmp, sizeof(tmp), "%lu", event->start);
         ast_string_field_set(event, uid, tmp);
      }
   }

   /* Get the attendees */
   for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
         prop; prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
      struct ast_calendar_attendee *attendee;
      const char *data;

      if (!(attendee = ast_calloc(1, sizeof(*attendee)))) {
         event = ast_calendar_unref_event(event);
         return;
      }
      data = icalproperty_get_attendee(prop);
      if (ast_strlen_zero(data)) {
         ast_free(attendee);
         continue;
      }
      attendee->data = ast_strdup(data);;
      AST_LIST_INSERT_TAIL(&event->attendees, attendee, next);
   }


   /* Only set values for alarm based on VALARM.  Can be overriden in main/calendar.c by autoreminder
    * therefore, go ahead and add events even if their is no VALARM or it is malformed
    * Currently we are only getting the first VALARM and are handling repitition in main/calendar.c from calendar.conf */
   if (!(valarm = icalcomponent_get_first_component(comp, ICAL_VALARM_COMPONENT))) {
      ao2_link(pvt->events, event);
      event = ast_calendar_unref_event(event);
      return;
   }

   if (!(prop = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY))) {
      ast_log(LOG_WARNING, "VALARM has no TRIGGER, skipping!\n");
      ao2_link(pvt->events, event);
      event = ast_calendar_unref_event(event);
      return;
   }

   trigger = icalproperty_get_trigger(prop);

   if (icaltriggertype_is_null_trigger(trigger)) {
      ast_log(LOG_WARNING, "Bad TRIGGER for VALARM, skipping!\n");
      ao2_link(pvt->events, event);
      event = ast_calendar_unref_event(event);
      return;
   }

   if (!icaltime_is_null_time(trigger.time)) { /* This is an absolute time */
      tmp = icaltime_convert_to_zone(trigger.time, utc);
      event->alarm = icaltime_as_timet_with_zone(tmp, utc);
   } else { /* Offset from either dtstart or dtend */
      /* XXX Technically you can check RELATED to see if the event fires from the END of the event
       * But, I'm not sure I've ever seen anyone implement it in calendaring software, so I'm ignoring for now */
      tmp = icaltime_add(start, trigger.duration);
      event->alarm = icaltime_as_timet_with_zone(tmp, icaltime_get_timezone(start));
   }

   ao2_link(pvt->events, event);
   event = ast_calendar_unref_event(event);

   return;
}
static void icalendar_destructor ( void *  obj) [static]

Definition at line 70 of file res_calendar_icalendar.c.

References ao2_callback, ao2_ref, ast_debug, ast_string_field_free_memory, icalendar_pvt::data, icalendar_pvt::events, ast_calendar::name, OBJ_MULTIPLE, OBJ_NODATA, OBJ_UNLINK, icalendar_pvt::owner, and icalendar_pvt::session.

Referenced by ical_load_calendar().

{
   struct icalendar_pvt *pvt = obj;

   ast_debug(1, "Destroying pvt for iCalendar %s\n", pvt->owner->name);
   if (pvt->session) {
      ne_session_destroy(pvt->session);
   }
   if (pvt->data) {
      icalcomponent_free(pvt->data);
   }
   ast_string_field_free_memory(pvt);

   ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);

   ao2_ref(pvt->events, -1);
}
static void icalendar_update_events ( struct icalendar_pvt pvt) [static]

Definition at line 314 of file res_calendar_icalendar.c.

References ast_calendar_merge_events(), ast_log(), icalendar_pvt::data, icalendar_pvt::events, icalendar_add_event(), LOG_ERROR, icalendar_pvt::owner, and ast_calendar::timeframe.

Referenced by ical_load_calendar().

{
   struct icaltimetype start_time, end_time;
   icalcomponent *iter;

   if (!pvt) {
      ast_log(LOG_ERROR, "iCalendar is NULL\n");
      return;
   }

   if (!pvt->owner) {
      ast_log(LOG_ERROR, "iCalendar is an orphan!\n");
      return;
   }

   if (!pvt->data) {
      ast_log(LOG_ERROR, "The iCalendar has not been parsed!\n");
      return;
   }

   start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
   end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
   end_time.second += pvt->owner->timeframe * 60;
   icaltime_normalize(end_time);

   for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT);
        iter;
       iter = icalcomponent_get_next_component(pvt->data, ICAL_VEVENT_COMPONENT))
   {
      icalcomponent_foreach_recurrence(iter, start_time, end_time, icalendar_add_event, pvt);
   }

   ast_calendar_merge_events(pvt->owner, pvt->events);
}
static time_t icalfloat_to_timet ( icaltimetype  time) [static]

Definition at line 163 of file res_calendar_icalendar.c.

References ast_mktime(), ast_tm::tm_hour, ast_tm::tm_isdst, ast_tm::tm_mday, ast_tm::tm_min, ast_tm::tm_mon, ast_tm::tm_sec, and ast_tm::tm_year.

Referenced by icalendar_add_event().

{
   struct ast_tm tm = {0,};
   struct timeval tv;

   tm.tm_mday = time.day;
   tm.tm_mon = time.month - 1;
   tm.tm_year = time.year - 1900;
   tm.tm_hour = time.hour;
   tm.tm_min = time.minute;
   tm.tm_sec = time.second;
   tm.tm_isdst = -1;
   tv = ast_mktime(&tm, NULL);

   return tv.tv_sec;
}
static int load_module ( void  ) [static]

Definition at line 487 of file res_calendar_icalendar.c.

References ast_calendar_register(), AST_MODULE_LOAD_DECLINE, and AST_MODULE_LOAD_SUCCESS.

{
   ne_sock_init();
   if (ast_calendar_register(&ical_tech)) {
      ne_sock_exit();
      return AST_MODULE_LOAD_DECLINE;
   }

   return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module ( void  ) [static]

Definition at line 498 of file res_calendar_icalendar.c.

References ast_calendar_unregister().

{
   ast_calendar_unregister(&ical_tech);
   ne_sock_exit();
   return 0;
}
static void * unref_icalendar ( void *  obj) [static]

Definition at line 88 of file res_calendar_icalendar.c.

References ao2_ref.

Referenced by ical_load_calendar().

{
   struct icalendar_pvt *pvt = obj;

   ao2_ref(pvt, -1);
   return NULL;
}

Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Asterisk iCalendar .ics file integration" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEVSTATE_PLUGIN, } [static]

Definition at line 509 of file res_calendar_icalendar.c.

Definition at line 509 of file res_calendar_icalendar.c.

struct ast_calendar_tech ical_tech [static]

Definition at line 49 of file res_calendar_icalendar.c.