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"
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 = "ac1f6a56484a8820659555499174e588" , .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEVSTATE_PLUGIN, } |
static struct ast_module_info * | ast_module_info = &__mod_info |
static struct ast_calendar_tech | ical_tech |
Resource for handling iCalendar calendars.
Definition in file res_calendar_icalendar.c.
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, icalendar_pvt::owner, icalendar_pvt::secret, and icalendar_pvt::user.
Referenced by ical_load_calendar().
00113 { 00114 struct icalendar_pvt *pvt = userdata; 00115 00116 if (attempts > 1) { 00117 ast_log(LOG_WARNING, "Invalid username or password for iCalendar '%s'\n", pvt->owner->name); 00118 return -1; 00119 } 00120 00121 ne_strnzcpy(username, pvt->user, NE_ABUFSIZ); 00122 ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ); 00123 00124 return 0; 00125 }
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, icalendar_pvt::owner, icalendar_pvt::session, icalendar_pvt::uri, and icalendar_pvt::url.
Referenced by ical_load_calendar().
00128 { 00129 int ret; 00130 struct ast_str *response; 00131 ne_request *req; 00132 icalcomponent *comp = NULL; 00133 00134 if (!pvt) { 00135 ast_log(LOG_ERROR, "There is no private!\n"); 00136 return NULL; 00137 } 00138 00139 if (!(response = ast_str_create(512))) { 00140 ast_log(LOG_ERROR, "Could not allocate memory for response.\n"); 00141 return NULL; 00142 } 00143 00144 req = ne_request_create(pvt->session, "GET", pvt->uri.path); 00145 ne_add_response_body_reader(req, ne_accept_2xx, fetch_response_reader, &response); 00146 00147 ret = ne_request_dispatch(req); 00148 ne_request_destroy(req); 00149 if (ret != NE_OK || !ast_str_strlen(response)) { 00150 ast_log(LOG_WARNING, "Unable to retrieve iCalendar '%s' from '%s': %s\n", pvt->owner->name, pvt->url, ne_get_error(pvt->session)); 00151 ast_free(response); 00152 return NULL; 00153 } 00154 00155 if (!ast_strlen_zero(ast_str_buffer(response))) { 00156 comp = icalparser_parse_string(ast_str_buffer(response)); 00157 } 00158 ast_free(response); 00159 00160 return comp; 00161 }
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, and ast_str_append().
Referenced by fetch_icalendar().
00097 { 00098 struct ast_str **response = data; 00099 unsigned char *tmp; 00100 00101 if (!(tmp = ast_malloc(len + 1))) { 00102 return -1; 00103 } 00104 memcpy(tmp, block, len); 00105 tmp[len] = '\0'; 00106 ast_str_append(response, 0, "%s", tmp); 00107 ast_free(tmp); 00108 00109 return 0; 00110 }
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_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.
00350 { 00351 struct icalendar_pvt *pvt; 00352 const struct ast_config *cfg; 00353 struct ast_variable *v; 00354 struct ast_calendar *cal = void_data; 00355 ast_mutex_t refreshlock; 00356 00357 if (!(cal && (cfg = ast_calendar_config_acquire()))) { 00358 ast_log(LOG_ERROR, "You must enable calendar support for res_icalendar to load\n"); 00359 return NULL; 00360 } 00361 if (ao2_trylock(cal)) { 00362 if (cal->unloading) { 00363 ast_log(LOG_WARNING, "Unloading module, load_calendar cancelled.\n"); 00364 } else { 00365 ast_log(LOG_WARNING, "Could not lock calendar, aborting!\n"); 00366 } 00367 ast_calendar_config_release(); 00368 return NULL; 00369 } 00370 00371 if (!(pvt = ao2_alloc(sizeof(*pvt), icalendar_destructor))) { 00372 ast_log(LOG_ERROR, "Could not allocate icalendar_pvt structure for calendar: %s\n", cal->name); 00373 ast_calendar_config_release(); 00374 return NULL; 00375 } 00376 00377 pvt->owner = cal; 00378 00379 if (!(pvt->events = ast_calendar_event_container_alloc())) { 00380 ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name); 00381 pvt = unref_icalendar(pvt); 00382 ao2_unlock(cal); 00383 ast_calendar_config_release(); 00384 return NULL; 00385 } 00386 00387 if (ast_string_field_init(pvt, 32)) { 00388 ast_log(LOG_ERROR, "Couldn't allocate string field space for calendar: %s\n", cal->name); 00389 pvt = unref_icalendar(pvt); 00390 ao2_unlock(cal); 00391 ast_calendar_config_release(); 00392 return NULL; 00393 } 00394 00395 for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) { 00396 if (!strcasecmp(v->name, "url")) { 00397 ast_string_field_set(pvt, url, v->value); 00398 } else if (!strcasecmp(v->name, "user")) { 00399 ast_string_field_set(pvt, user, v->value); 00400 } else if (!strcasecmp(v->name, "secret")) { 00401 ast_string_field_set(pvt, secret, v->value); 00402 } 00403 } 00404 00405 ast_calendar_config_release(); 00406 00407 if (ast_strlen_zero(pvt->url)) { 00408 ast_log(LOG_WARNING, "No URL was specified for iCalendar '%s' - skipping.\n", cal->name); 00409 pvt = unref_icalendar(pvt); 00410 ao2_unlock(cal); 00411 return NULL; 00412 } 00413 00414 if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) { 00415 ast_log(LOG_WARNING, "Could not parse url '%s' for iCalendar '%s' - skipping.\n", pvt->url, cal->name); 00416 pvt = unref_icalendar(pvt); 00417 ao2_unlock(cal); 00418 return NULL; 00419 } 00420 00421 if (pvt->uri.scheme == NULL) { 00422 pvt->uri.scheme = "http"; 00423 } 00424 00425 if (pvt->uri.port == 0) { 00426 pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme); 00427 } 00428 00429 pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port); 00430 ne_redirect_register(pvt->session); 00431 ne_set_server_auth(pvt->session, auth_credentials, pvt); 00432 if (!strcasecmp(pvt->uri.scheme, "https")) { 00433 ne_ssl_trust_default_ca(pvt->session); 00434 } 00435 00436 cal->tech_pvt = pvt; 00437 00438 ast_mutex_init(&refreshlock); 00439 00440 /* Load it the first time */ 00441 if (!(pvt->data = fetch_icalendar(pvt))) { 00442 ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", cal->name); 00443 } 00444 00445 icalendar_update_events(pvt); 00446 00447 ao2_unlock(cal); 00448 00449 /* The only writing from another thread will be if unload is true */ 00450 for(;;) { 00451 struct timeval tv = ast_tvnow(); 00452 struct timespec ts = {0,}; 00453 00454 ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh); 00455 00456 ast_mutex_lock(&refreshlock); 00457 while (!pvt->owner->unloading) { 00458 if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) { 00459 break; 00460 } 00461 } 00462 ast_mutex_unlock(&refreshlock); 00463 00464 if (pvt->owner->unloading) { 00465 ast_debug(10, "Skipping refresh since we got a shutdown signal\n"); 00466 return NULL; 00467 } 00468 00469 ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh); 00470 00471 /* Free the old calendar data */ 00472 if (pvt->data) { 00473 icalcomponent_free(pvt->data); 00474 pvt->data = NULL; 00475 } 00476 if (!(pvt->data = fetch_icalendar(pvt))) { 00477 ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", pvt->owner->name); 00478 continue; 00479 } 00480 00481 icalendar_update_events(pvt); 00482 } 00483 00484 return NULL; 00485 }
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_attendee::data, ast_calendar_event::end, icalendar_pvt::events, icalfloat_to_timet(), LOG_ERROR, LOG_WARNING, icalendar_pvt::owner, and ast_calendar_event::start.
Referenced by icalendar_update_events().
00190 { 00191 struct icalendar_pvt *pvt = data; 00192 struct ast_calendar_event *event; 00193 icaltimezone *utc = icaltimezone_get_utc_timezone(); 00194 icaltimetype start, end, tmp; 00195 icalcomponent *valarm; 00196 icalproperty *prop; 00197 struct icaltriggertype trigger; 00198 00199 if (!(pvt && pvt->owner)) { 00200 ast_log(LOG_ERROR, "Require a private structure with an ownenr\n"); 00201 return; 00202 } 00203 00204 if (!(event = ast_calendar_event_alloc(pvt->owner))) { 00205 ast_log(LOG_ERROR, "Could not allocate an event!\n"); 00206 return; 00207 } 00208 00209 start = icalcomponent_get_dtstart(comp); 00210 end = icalcomponent_get_dtend(comp); 00211 00212 event->start = icaltime_get_tzid(start) ? span->start : icalfloat_to_timet(start); 00213 event->end = icaltime_get_tzid(end) ? span->end : icalfloat_to_timet(end); 00214 event->busy_state = span->is_busy ? AST_CALENDAR_BS_BUSY : AST_CALENDAR_BS_FREE; 00215 00216 if ((prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY))) { 00217 ast_string_field_set(event, summary, icalproperty_get_value_as_string(prop)); 00218 } 00219 00220 if ((prop = icalcomponent_get_first_property(comp, ICAL_DESCRIPTION_PROPERTY))) { 00221 ast_string_field_set(event, description, icalproperty_get_value_as_string(prop)); 00222 } 00223 00224 if ((prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY))) { 00225 ast_string_field_set(event, organizer, icalproperty_get_value_as_string(prop)); 00226 } 00227 00228 if ((prop = icalcomponent_get_first_property(comp, ICAL_LOCATION_PROPERTY))) { 00229 ast_string_field_set(event, location, icalproperty_get_value_as_string(prop)); 00230 } 00231 00232 if ((prop = icalcomponent_get_first_property(comp, ICAL_CATEGORIES_PROPERTY))) { 00233 ast_string_field_set(event, categories, icalproperty_get_value_as_string(prop)); 00234 } 00235 00236 if ((prop = icalcomponent_get_first_property(comp, ICAL_PRIORITY_PROPERTY))) { 00237 event->priority = icalvalue_get_integer(icalproperty_get_value(prop)); 00238 } 00239 00240 if ((prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY))) { 00241 ast_string_field_set(event, uid, icalproperty_get_value_as_string(prop)); 00242 } else { 00243 ast_log(LOG_WARNING, "No UID found, but one is required. Generating, but updates may not be acurate\n"); 00244 if (!ast_strlen_zero(event->summary)) { 00245 ast_string_field_set(event, uid, event->summary); 00246 } else { 00247 char tmp[100]; 00248 snprintf(tmp, sizeof(tmp), "%ld", event->start); 00249 ast_string_field_set(event, uid, tmp); 00250 } 00251 } 00252 00253 /* Get the attendees */ 00254 for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); 00255 prop; prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) { 00256 struct ast_calendar_attendee *attendee; 00257 const char *data; 00258 00259 if (!(attendee = ast_calloc(1, sizeof(*attendee)))) { 00260 event = ast_calendar_unref_event(event); 00261 return; 00262 } 00263 data = icalproperty_get_attendee(prop); 00264 if (ast_strlen_zero(data)) { 00265 ast_free(attendee); 00266 continue; 00267 } 00268 attendee->data = ast_strdup(data);; 00269 AST_LIST_INSERT_TAIL(&event->attendees, attendee, next); 00270 } 00271 00272 00273 /* Only set values for alarm based on VALARM. Can be overriden in main/calendar.c by autoreminder 00274 * therefore, go ahead and add events even if their is no VALARM or it is malformed 00275 * Currently we are only getting the first VALARM and are handling repitition in main/calendar.c from calendar.conf */ 00276 if (!(valarm = icalcomponent_get_first_component(comp, ICAL_VALARM_COMPONENT))) { 00277 ao2_link(pvt->events, event); 00278 event = ast_calendar_unref_event(event); 00279 return; 00280 } 00281 00282 if (!(prop = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY))) { 00283 ast_log(LOG_WARNING, "VALARM has no TRIGGER, skipping!\n"); 00284 ao2_link(pvt->events, event); 00285 event = ast_calendar_unref_event(event); 00286 return; 00287 } 00288 00289 trigger = icalproperty_get_trigger(prop); 00290 00291 if (icaltriggertype_is_null_trigger(trigger)) { 00292 ast_log(LOG_WARNING, "Bad TRIGGER for VALARM, skipping!\n"); 00293 ao2_link(pvt->events, event); 00294 event = ast_calendar_unref_event(event); 00295 return; 00296 } 00297 00298 if (!icaltime_is_null_time(trigger.time)) { /* This is an absolute time */ 00299 tmp = icaltime_convert_to_zone(trigger.time, utc); 00300 event->alarm = icaltime_as_timet_with_zone(tmp, utc); 00301 } else { /* Offset from either dtstart or dtend */ 00302 /* XXX Technically you can check RELATED to see if the event fires from the END of the event 00303 * But, I'm not sure I've ever seen anyone implement it in calendaring software, so I'm ignoring for now */ 00304 tmp = icaltime_add(start, trigger.duration); 00305 event->alarm = icaltime_as_timet_with_zone(tmp, utc); 00306 } 00307 00308 ao2_link(pvt->events, event); 00309 event = ast_calendar_unref_event(event); 00310 00311 return; 00312 }
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, OBJ_MULTIPLE, OBJ_NODATA, OBJ_UNLINK, icalendar_pvt::owner, and icalendar_pvt::session.
Referenced by ical_load_calendar().
00071 { 00072 struct icalendar_pvt *pvt = obj; 00073 00074 ast_debug(1, "Destroying pvt for iCalendar %s\n", pvt->owner->name); 00075 if (pvt->session) { 00076 ne_session_destroy(pvt->session); 00077 } 00078 if (pvt->data) { 00079 icalcomponent_free(pvt->data); 00080 } 00081 ast_string_field_free_memory(pvt); 00082 00083 ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL); 00084 00085 ao2_ref(pvt->events, -1); 00086 }
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().
00315 { 00316 struct icaltimetype start_time, end_time; 00317 icalcomponent *iter; 00318 00319 if (!pvt) { 00320 ast_log(LOG_ERROR, "iCalendar is NULL\n"); 00321 return; 00322 } 00323 00324 if (!pvt->owner) { 00325 ast_log(LOG_ERROR, "iCalendar is an orphan!\n"); 00326 return; 00327 } 00328 00329 if (!pvt->data) { 00330 ast_log(LOG_ERROR, "The iCalendar has not been parsed!\n"); 00331 return; 00332 } 00333 00334 start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone()); 00335 end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone()); 00336 end_time.second += pvt->owner->timeframe * 60; 00337 icaltime_normalize(end_time); 00338 00339 for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT); 00340 iter; 00341 iter = icalcomponent_get_next_component(pvt->data, ICAL_VEVENT_COMPONENT)) 00342 { 00343 icalcomponent_foreach_recurrence(iter, start_time, end_time, icalendar_add_event, pvt); 00344 } 00345 00346 ast_calendar_merge_events(pvt->owner, pvt->events); 00347 }
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().
00164 { 00165 struct ast_tm tm = {0,}; 00166 struct timeval tv; 00167 00168 tm.tm_mday = time.day; 00169 tm.tm_mon = time.month - 1; 00170 tm.tm_year = time.year - 1900; 00171 tm.tm_hour = time.hour; 00172 tm.tm_min = time.minute; 00173 tm.tm_sec = time.second; 00174 tm.tm_isdst = -1; 00175 tv = ast_mktime(&tm, NULL); 00176 00177 return tv.tv_sec; 00178 }
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.
00488 { 00489 ne_sock_init(); 00490 if (ast_calendar_register(&ical_tech)) { 00491 ne_sock_exit(); 00492 return AST_MODULE_LOAD_DECLINE; 00493 } 00494 00495 return AST_MODULE_LOAD_SUCCESS; 00496 }
static int unload_module | ( | void | ) | [static] |
Definition at line 498 of file res_calendar_icalendar.c.
References ast_calendar_unregister().
00499 { 00500 ast_calendar_unregister(&ical_tech); 00501 ne_sock_exit(); 00502 return 0; 00503 }
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().
00089 { 00090 struct icalendar_pvt *pvt = obj; 00091 00092 ao2_ref(pvt, -1); 00093 return NULL; 00094 }
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 = "ac1f6a56484a8820659555499174e588" , .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEVSTATE_PLUGIN, } [static] |
Definition at line 509 of file res_calendar_icalendar.c.
struct ast_module_info* ast_module_info = &__mod_info [static] |
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.