diff --git a/main/app.c b/main/app.c index c6a176e650..6c7cb2895b 100644 --- a/main/app.c +++ b/main/app.c @@ -274,18 +274,21 @@ static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsg static int (*ast_inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs) = NULL; static int (*ast_sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context) = NULL; static int (*ast_messagecount_func)(const char *context, const char *mailbox, const char *folder) = NULL; +static int (*ast_copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data) = NULL; void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, const char *folder), int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs), int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs), int (*messagecount_func)(const char *context, const char *mailbox, const char *folder), - int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context)) + int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context), + int (*copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data)) { ast_has_voicemail_func = has_voicemail_func; ast_inboxcount_func = inboxcount_func; ast_inboxcount2_func = inboxcount2_func; ast_messagecount_func = messagecount_func; ast_sayname_func = sayname_func; + ast_copy_recording_to_vm_func = copy_recording_to_vm_func; } void ast_uninstall_vm_functions(void) @@ -295,6 +298,7 @@ void ast_uninstall_vm_functions(void) ast_inboxcount2_func = NULL; ast_messagecount_func = NULL; ast_sayname_func = NULL; + ast_copy_recording_to_vm_func = NULL; } int ast_app_has_voicemail(const char *mailbox, const char *folder) @@ -309,7 +313,29 @@ int ast_app_has_voicemail(const char *mailbox, const char *folder) } return 0; } + +/*! + * \internal + * \brief Function used as a callback for ast_copy_recording_to_vm when a real one isn't installed. + * \param vm_rec_data Stores crucial information about the voicemail that will basically just be used + * to figure out what the name of the recipient was supposed to be + */ +int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data) +{ + static int warned = 0; + + if (ast_copy_recording_to_vm_func) { + return ast_copy_recording_to_vm_func(vm_rec_data); + } + + if (warned++ % 10 == 0) { + ast_verb(3, "copy recording to voicemail called to copy %s.%s to %s@%s, but voicemail not loaded.\n", + vm_rec_data->recording_file, vm_rec_data->recording_ext, + vm_rec_data->mailbox, vm_rec_data->context); + } + return -1; +} int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs) { @@ -560,10 +586,16 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in return res; } -int ast_control_streamfile(struct ast_channel *chan, const char *file, - const char *fwd, const char *rev, - const char *stop, const char *suspend, - const char *restart, int skipms, long *offsetms) +static int control_streamfile(struct ast_channel *chan, + const char *file, + const char *fwd, + const char *rev, + const char *stop, + const char *suspend, + const char *restart, + int skipms, + long *offsetms, + ast_waitstream_fr_cb cb) { char *breaks = NULL; char *end = NULL; @@ -634,7 +666,11 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file, ast_seekstream(chan->stream, offset, SEEK_SET); offset = 0; } - res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms); + if (cb) { + res = ast_waitstream_fr_w_cb(chan, breaks, fwd, rev, skipms, cb); + } else { + res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms); + } } if (res < 1) { @@ -698,6 +734,28 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file, return res; } +int ast_control_streamfile_w_cb(struct ast_channel *chan, + const char *file, + const char *fwd, + const char *rev, + const char *stop, + const char *suspend, + const char *restart, + int skipms, + long *offsetms, + ast_waitstream_fr_cb cb) +{ + return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, cb); +} + +int ast_control_streamfile(struct ast_channel *chan, const char *file, + const char *fwd, const char *rev, + const char *stop, const char *suspend, + const char *restart, int skipms, long *offsetms) +{ + return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL); +} + int ast_play_and_wait(struct ast_channel *chan, const char *fn) { int d = 0; diff --git a/main/asterisk.c b/main/asterisk.c index e0e47ab933..655368f3bf 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -136,6 +136,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/ast_version.h" #include "asterisk/linkedlists.h" #include "asterisk/devicestate.h" +#include "asterisk/presencestate.h" #include "asterisk/module.h" #include "asterisk/dsp.h" #include "asterisk/buildinfo.h" @@ -3893,6 +3894,11 @@ int main(int argc, char *argv[]) exit(1); } + if (ast_presence_state_engine_init()) { + printf("%s", term_quit()); + exit(1); + } + ast_dsp_init(); ast_udptl_init(); diff --git a/main/channel.c b/main/channel.c index 807aeb78f5..5e7b88050d 100644 --- a/main/channel.c +++ b/main/channel.c @@ -4365,6 +4365,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con case AST_CONTROL_CC: case AST_CONTROL_READ_ACTION: case AST_CONTROL_AOC: + case AST_CONTROL_CUSTOM: case AST_CONTROL_END_OF_Q: case AST_CONTROL_MCID: case AST_CONTROL_UPDATE_RTP_PEER: @@ -4554,6 +4555,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, case AST_CONTROL_CC: case AST_CONTROL_READ_ACTION: case AST_CONTROL_AOC: + case AST_CONTROL_CUSTOM: case AST_CONTROL_END_OF_Q: case AST_CONTROL_MCID: case AST_CONTROL_UPDATE_RTP_PEER: diff --git a/main/config.c b/main/config.c index 271af96437..3977b67de9 100644 --- a/main/config.c +++ b/main/config.c @@ -65,6 +65,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") static char *extconfig_conf = "extconfig.conf"; +static struct ao2_container *cfg_hooks; +static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg); + /*! \brief Structure to keep comments for rewriting configuration files */ struct ast_comment { struct ast_comment *next; @@ -2265,12 +2268,44 @@ static struct ast_config_engine text_file_engine = { .load_func = config_text_file_load, }; +struct ast_config *ast_config_copy(const struct ast_config *old) +{ + struct ast_config *new_config = ast_config_new(); + struct ast_category *cat_iter; + + if (!new_config) { + return NULL; + } + + for (cat_iter = old->root; cat_iter; cat_iter = cat_iter->next) { + struct ast_category *new_cat = + ast_category_new(cat_iter->name, cat_iter->file, cat_iter->lineno); + if (!new_cat) { + goto fail; + } + ast_category_append(new_config, new_cat); + if (cat_iter->root) { + new_cat->root = ast_variables_dup(cat_iter->root); + if (!new_cat->root) { + goto fail; + } + new_cat->last = cat_iter->last; + } + } + + return new_config; + +fail: + ast_config_destroy(new_config); + return NULL; +} + struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked) { char db[256]; char table[256]; struct ast_config_engine *loader = &text_file_engine; - struct ast_config *result; + struct ast_config *result; /* The config file itself bumps include_level by 1 */ if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) { @@ -2297,10 +2332,12 @@ struct ast_config *ast_config_internal_load(const char *filename, struct ast_con result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked); - if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED) + if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED) { result->include_level--; - else if (result != CONFIG_STATUS_FILEINVALID) + config_hook_exec(filename, who_asked, result); + } else if (result != CONFIG_STATUS_FILEINVALID) { cfg->include_level--; + } return result; } @@ -2893,6 +2930,89 @@ static struct ast_cli_entry cli_config[] = { AST_CLI_DEFINE(handle_cli_config_reload, "Force a reload on modules using a particular configuration file"), AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"), }; + +struct cfg_hook { + const char *name; + const char *filename; + const char *module; + config_hook_cb hook_cb; +}; + +static void hook_destroy(void *obj) +{ + struct cfg_hook *hook = obj; + ast_free((void *) hook->name); + ast_free((void *) hook->filename); + ast_free((void *) hook->module); +} + +static int hook_cmp(void *obj, void *arg, int flags) +{ + struct cfg_hook *hook1 = obj; + struct cfg_hook *hook2 = arg; + + return !(strcasecmp(hook1->name, hook2->name)) ? CMP_MATCH | CMP_STOP : 0; +} + +static int hook_hash(const void *obj, const int flags) +{ + const struct cfg_hook *hook = obj; + + return ast_str_hash(hook->name); +} + +void ast_config_hook_unregister(const char *name) +{ + struct cfg_hook tmp; + + tmp.name = ast_strdupa(name); + + ao2_find(cfg_hooks, &tmp, OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); +} + +static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg) +{ + struct ao2_iterator it; + struct cfg_hook *hook; + if (!(cfg_hooks)) { + return; + } + it = ao2_iterator_init(cfg_hooks, 0); + while ((hook = ao2_iterator_next(&it))) { + if (!strcasecmp(hook->filename, filename) && + !strcasecmp(hook->module, module)) { + struct ast_config *copy = ast_config_copy(cfg); + hook->hook_cb(copy); + } + ao2_ref(hook, -1); + } + ao2_iterator_destroy(&it); +} + +int ast_config_hook_register(const char *name, + const char *filename, + const char *module, + enum config_hook_flags flags, + config_hook_cb hook_cb) +{ + struct cfg_hook *hook; + if (!cfg_hooks && !(cfg_hooks = ao2_container_alloc(17, hook_hash, hook_cmp))) { + return -1; + } + + if (!(hook = ao2_alloc(sizeof(*hook), hook_destroy))) { + return -1; + } + + hook->hook_cb = hook_cb; + hook->filename = ast_strdup(filename); + hook->name = ast_strdup(name); + hook->module = ast_strdup(module); + + ao2_link(cfg_hooks, hook); + return 0; +} + int register_config_cli(void) { diff --git a/main/custom_control_frame.c b/main/custom_control_frame.c new file mode 100644 index 0000000000..adb4b8e41b --- /dev/null +++ b/main/custom_control_frame.c @@ -0,0 +1,191 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011-2012, Digium, Inc. + * + * David Vossel + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Encode and Decode custom control frame payload types. + * + * \author David Vossel + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/_private.h" + +#include "asterisk/custom_control_frame.h" + +struct ast_custom_payload { + enum ast_custom_payload_type type; + /*! length of data portion only */ + size_t datalen; + char *data; +}; + +enum ast_custom_payload_type ast_custom_payload_type(struct ast_custom_payload *type) +{ + return type->type; +} + +size_t ast_custom_payload_len(struct ast_custom_payload *type) +{ + return type->datalen + sizeof(struct ast_custom_payload); +} + +struct custom_sipinfo { + size_t num_headers; + int content_present; + int useragent_filter_present; + char *data; +}; + +struct ast_custom_payload *ast_custom_payload_sipinfo_encode(struct ast_variable *headers, + const char *content_type, + const char *content, + const char *useragent_filter) +{ + int num_headers = 0; + int content_present = 0; + int content_strlen = 0; + int content_type_strlen = 0; + int useragent_filter_present = 0; + int useragent_filter_len = 0; + size_t datalen = 0; + struct ast_variable *var; + struct ast_custom_payload *payload; + struct custom_sipinfo *sipinfo; + char *data; + + datalen += sizeof(struct custom_sipinfo); + + for (var = headers; var; var = var->next) { + datalen += strlen(var->name) + 1; + datalen += strlen(var->value) + 1; + num_headers++; + } + + if (!ast_strlen_zero(content_type) && !ast_strlen_zero(content)) { + content_type_strlen = strlen(content_type); + content_strlen = strlen(content); + datalen += content_type_strlen + 1; + datalen += content_strlen + 1; + content_present = 1; + } + + if (!ast_strlen_zero(useragent_filter)) { + useragent_filter_len = strlen(useragent_filter); + datalen += useragent_filter_len + 1; + useragent_filter_present = 1; + } + + if (!(payload = ast_calloc(1, datalen + sizeof(*payload)))) { + return NULL; + } + + payload->type = AST_CUSTOM_SIP_INFO; + payload->datalen = datalen; + payload->data = (char *) payload + sizeof(struct ast_custom_payload); + sipinfo = (struct custom_sipinfo *) payload->data; + sipinfo->num_headers = num_headers; + sipinfo->content_present = content_present; + sipinfo->useragent_filter_present = useragent_filter_present; + sipinfo->data = (char *) sipinfo + sizeof(struct custom_sipinfo); + + /* store string buffers in payload data + * headers are put in first, followed by content type and then content body. */ + data = sipinfo->data; + + for (var = headers; var; var = var->next) { + int namelen = strlen(var->name); + int vallen = strlen(var->value); + + /*! we already know we have enough room for each of these */ + ast_copy_string(data, var->name, namelen+1); + data += namelen + 1; /* skip over the '\0' character */ + ast_copy_string(data, var->value, vallen+1); + data += vallen + 1; /* skip over the '\0' character */ + } + + if (content_present) { + ast_copy_string(data, content_type, content_type_strlen+1); + data += content_type_strlen + 1; + ast_copy_string(data, content, content_strlen+1); + data += content_strlen + 1; + } + + if (useragent_filter_present) { + ast_copy_string(data, useragent_filter, useragent_filter_len+1); + } + + return payload; +} + +int ast_custom_payload_sipinfo_decode(struct ast_custom_payload *pl, + struct ast_variable **headers, + char **content_type, + char **content, + char **useragent_filter) +{ + struct custom_sipinfo *sipinfo; + struct ast_variable *cur = NULL; + char *data; + int i; + + *headers = NULL; + *content_type = NULL; + *content = NULL; + *useragent_filter = NULL; + + if (pl->type != AST_CUSTOM_SIP_INFO) { + return -1; + } + + sipinfo = (struct custom_sipinfo *) pl->data; + data = sipinfo->data; + for (i = 0; i < sipinfo->num_headers; i++) { + const char *name; + const char *value; + + name = data; + data += strlen(name) + 1; + value = data; + data += strlen(value) + 1; + + if (*headers) { + if ((cur->next = ast_variable_new(name, value, ""))) { + cur = cur->next; + } + } else { + *headers = cur = ast_variable_new(name, value, ""); + } + } + + if (sipinfo->content_present) { + *content_type = ast_strdup(data); + data += strlen(data) + 1; + *content = ast_strdup(data); + data += strlen(data) + 1; + } + + if (sipinfo->useragent_filter_present) { + *useragent_filter = ast_strdup(data); + } + return 0; +} + diff --git a/main/event.c b/main/event.c index eb1924b06e..1d71c76092 100644 --- a/main/event.c +++ b/main/event.c @@ -137,6 +137,7 @@ static int ast_event_cmp(void *obj, void *arg, int flags); static int ast_event_hash_mwi(const void *obj, const int flags); static int ast_event_hash_devstate(const void *obj, const int flags); static int ast_event_hash_devstate_change(const void *obj, const int flags); +static int ast_event_hash_presence_state_change(const void *obj, const int flags); #ifdef LOW_MEMORY #define NUM_CACHE_BUCKETS 17 @@ -181,6 +182,11 @@ static struct { .hash_fn = ast_event_hash_devstate_change, .cache_args = { AST_EVENT_IE_DEVICE, AST_EVENT_IE_EID, }, }, + [AST_EVENT_PRESENCE_STATE] = { + .hash_fn = ast_event_hash_presence_state_change, + .cache_args = { AST_EVENT_IE_PRESENCE_STATE, }, + }, + }; /*! @@ -1585,6 +1591,22 @@ static int ast_event_hash_devstate_change(const void *obj, const int flags) return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE)); } +/*! + * \internal + * \brief Hash function for AST_EVENT_PRESENCE_STATE + * + * \param[in] obj an ast_event + * \param[in] flags unused + * + * \return hash value + */ +static int ast_event_hash_presence_state_change(const void *obj, const int flags) +{ + const struct ast_event *event = obj; + + return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER)); +} + static int ast_event_hash(const void *obj, const int flags) { const struct ast_event_ref *event_ref; diff --git a/main/features.c b/main/features.c index a774870c99..2570679eda 100644 --- a/main/features.c +++ b/main/features.c @@ -49,6 +49,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/app.h" #include "asterisk/say.h" #include "asterisk/features.h" +#include "asterisk/custom_control_frame.h" #include "asterisk/musiconhold.h" #include "asterisk/config.h" #include "asterisk/cli.h" @@ -378,6 +379,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Bridge together two channels already in the PBX. + + + Get a list of parking lots + + + + + + List all parking lots as a series of AMI events + + ***/ #define DEFAULT_PARK_TIME 45000 /*!< ms */ @@ -7003,6 +7015,41 @@ static struct ast_cli_entry cli_features[] = { AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"), }; +static int manager_parkinglot_list(struct mansession *s, const struct message *m) +{ + const char *id = astman_get_header(m, "ActionID"); + char idText[256] = ""; + struct ao2_iterator iter; + struct ast_parkinglot *curlot; + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + + astman_send_ack(s, m, "Parking lots will follow"); + + iter = ao2_iterator_init(parkinglots, 0); + while ((curlot = ao2_iterator_next(&iter))) { + astman_append(s, "Event: Parkinglot\r\n" + "Name: %s\r\n" + "StartExten: %d\r\n" + "StopExten: %d\r\n" + "Timeout: %d\r\n" + "\r\n", + curlot->name, + curlot->cfg.parking_start, + curlot->cfg.parking_stop, + curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime); + ao2_ref(curlot, -1); + } + + astman_append(s, + "Event: ParkinglotsComplete\r\n" + "%s" + "\r\n",idText); + + return RESULT_SUCCESS; +} + /*! * \brief Dump parking lot status * \param s @@ -8137,6 +8184,7 @@ int ast_features_init(void) res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL); if (!res) { ast_manager_register_xml("ParkedCalls", 0, manager_parking_status); + ast_manager_register_xml("Parkinglots", 0, manager_parkinglot_list); ast_manager_register_xml("Park", EVENT_FLAG_CALL, manager_park); ast_manager_register_xml("Bridge", EVENT_FLAG_CALL, action_bridge); } diff --git a/main/file.c b/main/file.c index 70b971cb3a..186acd1d2a 100644 --- a/main/file.c +++ b/main/file.c @@ -1240,9 +1240,15 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con /*! * \brief the core of all waitstream() functions */ -static int waitstream_core(struct ast_channel *c, const char *breakon, - const char *forward, const char *reverse, int skip_ms, - int audiofd, int cmdfd, const char *context) +static int waitstream_core(struct ast_channel *c, + const char *breakon, + const char *forward, + const char *reverse, + int skip_ms, + int audiofd, + int cmdfd, + const char *context, + ast_waitstream_fr_cb cb) { const char *orig_chan_name = NULL; int err = 0; @@ -1260,6 +1266,11 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, if (ast_test_flag(c, AST_FLAG_MASQ_NOSTREAM)) orig_chan_name = ast_strdupa(c->name); + if (c->stream && cb) { + long ms_len = ast_tellstream(c->stream) / (ast_format_rate(c->stream->fmt->format) / 1000); + cb(c, ms_len, AST_WAITSTREAM_CB_START); + } + while (c->stream) { int res; int ms; @@ -1321,6 +1332,7 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, return res; } } else { + enum ast_waitstream_fr_cb_values cb_val = 0; res = fr->subclass.integer; if (strchr(forward, res)) { int eoftest; @@ -1331,13 +1343,19 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, } else { ungetc(eoftest, c->stream->f); } + cb_val = AST_WAITSTREAM_CB_FASTFORWARD; } else if (strchr(reverse, res)) { ast_stream_rewind(c->stream, skip_ms); + cb_val = AST_WAITSTREAM_CB_REWIND; } else if (strchr(breakon, res)) { ast_frfree(fr); ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY); return res; - } + } + if (cb_val && cb) { + long ms_len = ast_tellstream(c->stream) / (ast_format_rate(c->stream->fmt->format) / 1000); + cb(c, ms_len, cb_val); + } } break; case AST_FRAME_CONTROL: @@ -1387,21 +1405,32 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, return (err || c->_softhangup) ? -1 : 0; } +int ast_waitstream_fr_w_cb(struct ast_channel *c, + const char *breakon, + const char *forward, + const char *reverse, + int ms, + ast_waitstream_fr_cb cb) +{ + return waitstream_core(c, breakon, forward, reverse, ms, + -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, cb); +} + int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *reverse, int ms) { return waitstream_core(c, breakon, forward, reverse, ms, - -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */); + -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, NULL /* no callback */); } int ast_waitstream(struct ast_channel *c, const char *breakon) { - return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL); + return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL, NULL /* no callback */); } int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd) { return waitstream_core(c, breakon, NULL, NULL, 0, - audiofd, cmdfd, NULL /* no context */); + audiofd, cmdfd, NULL /* no context */, NULL /* no callback */); } int ast_waitstream_exten(struct ast_channel *c, const char *context) @@ -1412,7 +1441,7 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context) if (!context) context = c->context; return waitstream_core(c, NULL, NULL, NULL, 0, - -1, -1, context); + -1, -1, context, NULL /* no callback */); } /* diff --git a/main/manager.c b/main/manager.c index a924b94e42..702cc54a23 100644 --- a/main/manager.c +++ b/main/manager.c @@ -1216,6 +1216,7 @@ static const struct permalias { { EVENT_FLAG_CC, "cc" }, { EVENT_FLAG_AOC, "aoc" }, { EVENT_FLAG_TEST, "test" }, + { EVENT_FLAG_MESSAGE, "message" }, { INT_MAX, "all" }, { 0, "none" }, }; @@ -5330,10 +5331,17 @@ int ast_manager_unregister(char *action) return 0; } -static int manager_state_cb(const char *context, const char *exten, enum ast_extension_states state, void *data) +static int manager_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data) { /* Notify managers of change */ char hint[512]; + int state = info->exten_state; + + /* only interested in device state for this right now */ + if (info->reason != AST_HINT_UPDATE_DEVICE) { + return 0; + } + ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten); manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nHint: %s\r\nStatus: %d\r\n", exten, context, hint, state); diff --git a/main/pbx.c b/main/pbx.c index f9c8c0df00..7f3ac77bdb 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/musiconhold.h" #include "asterisk/app.h" #include "asterisk/devicestate.h" +#include "asterisk/presencestate.h" #include "asterisk/event.h" #include "asterisk/hashtab.h" #include "asterisk/module.h" @@ -801,7 +802,7 @@ AST_APP_OPTIONS(waitexten_opts, { struct ast_context; struct ast_app; -static struct ast_taskprocessor *device_state_tps; +static struct ast_taskprocessor *extension_state_tps; AST_THREADSTORAGE(switch_data); AST_THREADSTORAGE(extensionstate_buf); @@ -946,8 +947,16 @@ struct ast_hint { * Will never be NULL while the hint is in the hints container. */ struct ast_exten *exten; - struct ao2_container *callbacks; /*!< Callback container for this extension */ - int laststate; /*!< Last known state */ + struct ao2_container *callbacks; /*!< Device state callback container for this extension */ + + /*! Dev state variables */ + int laststate; /*!< Last known device state */ + + /*! Presence state variables */ + int last_presence_state; /*!< Last known presence state */ + char *last_presence_subtype; /*!< Last known presence subtype string */ + char *last_presence_message; /*!< Last known presence message string */ + char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */ char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */ }; @@ -1094,6 +1103,13 @@ static const struct cfextension_states { { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" } }; +struct presencechange { + char *provider; + int state; + char *subtype; + char *message; +}; + struct statechange { AST_LIST_ENTRY(statechange) entry; char dev[0]; @@ -1264,6 +1280,8 @@ static char *overrideswitch = NULL; /*! \brief Subscription for device state change events */ static struct ast_event_sub *device_state_sub; +/*! \brief Subscription for presence state change events */ +static struct ast_event_sub *presence_state_sub; AST_MUTEX_DEFINE_STATIC(maxcalllock); static int countcalls; @@ -3257,7 +3275,6 @@ const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, stru int offset, length; int i, need_substring; struct varshead *places[2] = { headp, &globals }; /* list of places where we may look */ - char workspace[20]; if (c) { ast_channel_lock(c); @@ -3353,6 +3370,7 @@ const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, stru } else if (!strcmp(var, "ASTLOGDIR")) { s = ast_config_AST_LOG_DIR; } else if (!strcmp(var, "ENTITYID")) { + char workspace[20]; ast_eid_to_str(workspace, sizeof(workspace), &ast_eid_default); s = workspace; } @@ -4473,6 +4491,42 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst return AST_EXTENSION_NOT_INUSE; } +/*! + * \internal + * \brief Parse out the presence portion of the hint string + */ +static char *parse_hint_presence(struct ast_str *hint_args) +{ + char *copy = ast_strdupa(ast_str_buffer(hint_args)); + char *tmp = ""; + + if ((tmp = strrchr(copy, ','))) { + *tmp = '\0'; + tmp++; + } else { + return NULL; + } + ast_str_set(&hint_args, 0, "%s", tmp); + return ast_str_buffer(hint_args); +} + +/*! + * \internal + * \brief Parse out the device portion of the hint string + */ +static char *parse_hint_device(struct ast_str *hint_args) +{ + char *copy = ast_strdupa(ast_str_buffer(hint_args)); + char *tmp; + + if ((tmp = strrchr(copy, ','))) { + *tmp = '\0'; + } + + ast_str_set(&hint_args, 0, "%s", copy); + return ast_str_buffer(hint_args); +} + static int ast_extension_state3(struct ast_str *hint_app) { char *cur; @@ -4480,7 +4534,7 @@ static int ast_extension_state3(struct ast_str *hint_app) struct ast_devstate_aggregate agg; /* One or more devices separated with a & character */ - rest = ast_str_buffer(hint_app); + rest = parse_hint_device(hint_app); ast_devstate_aggregate_init(&agg); while ((cur = strsep(&rest, "&"))) { @@ -4538,6 +4592,206 @@ int ast_extension_state(struct ast_channel *c, const char *context, const char * return ast_extension_state2(e); /* Check all devices in the hint */ } +static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message) +{ + struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32); + char *presence_provider; + const char *app; + + if (!e || !hint_app) { + return -1; + } + + app = ast_get_extension_app(e); + if (ast_strlen_zero(app)) { + return -1; + } + ast_str_set(&hint_app, 0, "%s", app); + presence_provider = parse_hint_presence(hint_app); + + if (ast_strlen_zero(presence_provider)) { + /* No presence string in the hint */ + return 0; + } + + return ast_presence_state(presence_provider, subtype, message); +} +int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message) +{ + struct ast_exten *e; + + if (!(e = ast_hint_extension(c, context, exten))) { /* Do we have a hint for this extension ? */ + return -1; /* No hint, return -1 */ + } + + if (e->exten[0] == '_') { + /* Create this hint on-the-fly */ + ast_add_extension(e->parent->name, 0, exten, e->priority, e->label, + e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr, + e->registrar); + if (!(e = ast_hint_extension(c, context, exten))) { + /* Improbable, but not impossible */ + return -1; + } + } + + return extension_presence_state_helper(e, subtype, message); +} + +static int execute_state_callback(ast_state_cb_type cb, + const char *context, + const char *exten, + void *data, + enum ast_state_cb_update_reason reason, + struct ast_hint *hint) +{ + int res = 0; + struct ast_state_cb_info info = { 0, }; + + info.reason = reason; + + /* Copy over current hint data */ + if (hint) { + ao2_lock(hint); + info.exten_state = hint->laststate; + info.presence_state = hint->last_presence_state; + if (!(ast_strlen_zero(hint->last_presence_subtype))) { + info.presence_subtype = ast_strdupa(hint->last_presence_subtype); + } else { + info.presence_subtype = ""; + } + if (!(ast_strlen_zero(hint->last_presence_message))) { + info.presence_message = ast_strdupa(hint->last_presence_message); + } else { + info.presence_message = ""; + } + ao2_unlock(hint); + } else { + info.exten_state = AST_EXTENSION_REMOVED; + } + + /* NOTE: The casts will not be needed for v10 and later */ + res = cb((char *) context, (char *) exten, &info, data); + + return res; +} + +static int handle_presencechange(void *datap) +{ + struct ast_hint *hint; + struct ast_str *hint_app = NULL; + struct presencechange *pc = datap; + struct ao2_iterator i; + struct ao2_iterator cb_iter; + char context_name[AST_MAX_CONTEXT]; + char exten_name[AST_MAX_EXTENSION]; + int res = -1; + + hint_app = ast_str_create(1024); + if (!hint_app) { + goto presencechange_cleanup; + } + + ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */ + i = ao2_iterator_init(hints, 0); + for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { + struct ast_state_cb *state_cb; + const char *app; + char *parse; + + ao2_lock(hint); + + if (!hint->exten) { + /* The extension has already been destroyed */ + ao2_unlock(hint); + continue; + } + + /* Does this hint monitor the device that changed state? */ + app = ast_get_extension_app(hint->exten); + if (ast_strlen_zero(app)) { + /* The hint does not monitor presence at all. */ + ao2_unlock(hint); + continue; + } + ast_str_set(&hint_app, 0, "%s", app); + parse = parse_hint_presence(hint_app); + if (ast_strlen_zero(parse)) { + ao2_unlock(hint); + continue; + } + if (strcasecmp(parse, pc->provider)) { + /* The hint does not monitor the presence provider. */ + ao2_unlock(hint); + continue; + } + + /* + * Save off strings in case the hint extension gets destroyed + * while we are notifying the watchers. + */ + ast_copy_string(context_name, + ast_get_context_name(ast_get_extension_context(hint->exten)), + sizeof(context_name)); + ast_copy_string(exten_name, ast_get_extension_name(hint->exten), + sizeof(exten_name)); + ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten)); + + /* Check to see if update is necessary */ + if ((hint->last_presence_state == pc->state) && + ((hint->last_presence_subtype && pc->subtype && !strcmp(hint->last_presence_subtype, pc->subtype)) || (!hint->last_presence_subtype && !pc->subtype)) && + ((hint->last_presence_message && pc->message && !strcmp(hint->last_presence_message, pc->message)) || (!hint->last_presence_message && !pc->message))) { + + /* this update is the same as the last, do nothing */ + ao2_unlock(hint); + continue; + } + + /* update new values */ + ast_free(hint->last_presence_subtype); + ast_free(hint->last_presence_message); + hint->last_presence_state = pc->state; + hint->last_presence_subtype = pc->subtype ? ast_strdup(pc->subtype) : NULL; + hint->last_presence_message = pc->message ? ast_strdup(pc->message) : NULL; + + ao2_unlock(hint); + + /* For general callbacks */ + cb_iter = ao2_iterator_init(statecbs, 0); + for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { + execute_state_callback(state_cb->change_cb, + context_name, + exten_name, + state_cb->data, + AST_HINT_UPDATE_PRESENCE, + hint); + } + ao2_iterator_destroy(&cb_iter); + + /* For extension callbacks */ + cb_iter = ao2_iterator_init(hint->callbacks, 0); + for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { + execute_state_callback(state_cb->change_cb, + context_name, + exten_name, + state_cb->data, + AST_HINT_UPDATE_PRESENCE, + hint); + } + ao2_iterator_destroy(&cb_iter); + } + ao2_iterator_destroy(&i); + ast_mutex_unlock(&context_merge_lock); + + res = 0; + +presencechange_cleanup: + ast_free(hint_app); + ao2_ref(pc, -1); + + return res; +} + static int handle_statechange(void *datap) { struct ast_hint *hint; @@ -4625,14 +4879,24 @@ static int handle_statechange(void *datap) /* For general callbacks */ cb_iter = ao2_iterator_init(statecbs, 0); for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - state_cb->change_cb(context_name, exten_name, state, state_cb->data); + execute_state_callback(state_cb->change_cb, + context_name, + exten_name, + state_cb->data, + AST_HINT_UPDATE_DEVICE, + hint); } ao2_iterator_destroy(&cb_iter); /* For extension callbacks */ cb_iter = ao2_iterator_init(hint->callbacks, 0); for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) { - state_cb->change_cb(context_name, exten_name, state, state_cb->data); + execute_state_callback(state_cb->change_cb, + context_name, + exten_name, + state_cb->data, + AST_HINT_UPDATE_DEVICE, + hint); } ao2_iterator_destroy(&cb_iter); } @@ -4804,7 +5068,6 @@ int ast_extension_state_del(int id, ast_state_cb_type change_cb) return ret; } - static int hint_id_cmp(void *obj, void *arg, int flags) { const struct ast_state_cb *cb = obj; @@ -4839,14 +5102,21 @@ static void destroy_hint(void *obj) context_name = hint->context_name; exten_name = hint->exten_name; } + hint->laststate = AST_EXTENSION_DEACTIVATED; while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) { /* Notify with -1 and remove all callbacks */ - state_cb->change_cb(context_name, exten_name, AST_EXTENSION_DEACTIVATED, - state_cb->data); + execute_state_callback(state_cb->change_cb, + context_name, + exten_name, + state_cb->data, + AST_HINT_UPDATE_DEVICE, + hint); ao2_ref(state_cb, -1); } ao2_ref(hint->callbacks, -1); } + ast_free(hint->last_presence_subtype); + ast_free(hint->last_presence_message); } /*! \brief Remove hint from extension */ @@ -4889,6 +5159,9 @@ static int ast_add_hint(struct ast_exten *e) { struct ast_hint *hint_new; struct ast_hint *hint_found; + char *message = NULL; + char *subtype = NULL; + int presence_state; if (!e) { return -1; @@ -4912,6 +5185,12 @@ static int ast_add_hint(struct ast_exten *e) } hint_new->exten = e; hint_new->laststate = ast_extension_state2(e); + if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) { + hint_new->last_presence_state = presence_state; + hint_new->last_presence_subtype = subtype; + hint_new->last_presence_message = message; + message = subtype = NULL; + } /* Prevent multiple add hints from adding the same hint at the same time. */ ao2_lock(hints); @@ -7402,6 +7681,10 @@ struct store_hint { char *exten; AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks; int laststate; + int last_presence_state; + char *last_presence_subtype; + char *last_presence_message; + AST_LIST_ENTRY(store_hint) list; char data[1]; }; @@ -7601,6 +7884,13 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ strcpy(saved_hint->data, hint->exten->parent->name); saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1; strcpy(saved_hint->exten, hint->exten->exten); + if (hint->last_presence_subtype) { + saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype); + } + if (hint->last_presence_message) { + saved_hint->last_presence_message = ast_strdup(hint->last_presence_message); + } + saved_hint->last_presence_state = hint->last_presence_state; ao2_unlock(hint); AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list); } @@ -7654,8 +7944,15 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ ao2_ref(thiscb, -1); } hint->laststate = saved_hint->laststate; + hint->last_presence_state = saved_hint->last_presence_state; + hint->last_presence_subtype = saved_hint->last_presence_subtype; + hint->last_presence_message = saved_hint->last_presence_message; ao2_unlock(hint); ao2_ref(hint, -1); + /* + * The free of saved_hint->last_presence_subtype and + * saved_hint->last_presence_message is not necessary here. + */ ast_free(saved_hint); } } @@ -7670,11 +7967,17 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) { /* this hint has been removed, notify the watchers */ while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) { - thiscb->change_cb(saved_hint->context, saved_hint->exten, - AST_EXTENSION_REMOVED, thiscb->data); + execute_state_callback(thiscb->change_cb, + saved_hint->context, + saved_hint->exten, + thiscb->data, + AST_HINT_UPDATE_DEVICE, + NULL); /* Ref that we added when putting into saved_hint->callbacks */ ao2_ref(thiscb, -1); } + ast_free(saved_hint->last_presence_subtype); + ast_free(saved_hint->last_presence_message); ast_free(saved_hint); } @@ -10369,6 +10672,51 @@ static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data) return res; } +static void presencechange_destroy(void *data) +{ + struct presencechange *pc = data; + ast_free(pc->provider); + ast_free(pc->subtype); + ast_free(pc->message); +} + +static void presence_state_cb(const struct ast_event *event, void *unused) +{ + struct presencechange *pc; + const char *tmp; + + if (!(pc = ao2_alloc(sizeof(*pc), presencechange_destroy))) { + return; + } + + tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER); + if (ast_strlen_zero(tmp)) { + ast_log(LOG_ERROR, "Received invalid event that had no presence provider IE\n"); + ao2_ref(pc, -1); + return; + } + pc->provider = ast_strdup(tmp); + + pc->state = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE); + if (pc->state < 0) { + ao2_ref(pc, -1); + return; + } + + if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE))) { + pc->subtype = ast_strdup(tmp); + } + + if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE))) { + pc->message = ast_strdup(tmp); + } + + /* The task processor thread is taking our reference to the presencechange object. */ + if (ast_taskprocessor_push(extension_state_tps, handle_presencechange, pc) < 0) { + ao2_ref(pc, -1); + } +} + static void device_state_cb(const struct ast_event *event, void *unused) { const char *device; @@ -10383,7 +10731,7 @@ static void device_state_cb(const struct ast_event *event, void *unused) if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(device) + 1))) return; strcpy(sc->dev, device); - if (ast_taskprocessor_push(device_state_tps, handle_statechange, sc) < 0) { + if (ast_taskprocessor_push(extension_state_tps, handle_statechange, sc) < 0) { ast_free(sc); } } @@ -10415,6 +10763,9 @@ static int hints_data_provider_get(const struct ast_data_search *search, ast_data_add_str(data_hint, "context", ast_get_context_name(ast_get_extension_context(hint->exten))); ast_data_add_str(data_hint, "application", ast_get_extension_app(hint->exten)); ast_data_add_str(data_hint, "state", ast_extension_state2str(hint->laststate)); + ast_data_add_str(data_hint, "presence_state", ast_presence_state2str(hint->last_presence_state)); + ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_subtype, "")); + ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_message, "")); ast_data_add_int(data_hint, "watchers", watchers); if (!ast_data_search_match(search, data_hint)) { @@ -10441,7 +10792,7 @@ int load_pbx(void) /* Initialize the PBX */ ast_verb(1, "Asterisk PBX Core Initializing\n"); - if (!(device_state_tps = ast_taskprocessor_get("pbx-core", 0))) { + if (!(extension_state_tps = ast_taskprocessor_get("pbx-core", 0))) { ast_log(LOG_WARNING, "failed to create pbx-core taskprocessor\n"); } @@ -10468,6 +10819,11 @@ int load_pbx(void) return -1; } + if (!(presence_state_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE, presence_state_cb, "pbx Presence State Change", NULL, + AST_EVENT_IE_END))) { + return -1; + } + return 0; } diff --git a/main/presencestate.c b/main/presencestate.c new file mode 100644 index 0000000000..18b3098f3c --- /dev/null +++ b/main/presencestate.c @@ -0,0 +1,285 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011-2012, Digium, Inc. + * + * David Vossel + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Presence state management + */ +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/_private.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/linkedlists.h" +#include "asterisk/presencestate.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/event.h" + +/*! \brief Device state strings for printing */ +static const struct { + const char *string; + enum ast_presence_state state; + +} state2string[] = { + { "not_set", AST_PRESENCE_NOT_SET}, + { "unavailable", AST_PRESENCE_UNAVAILABLE }, + { "available", AST_PRESENCE_AVAILABLE}, + { "away", AST_PRESENCE_AWAY}, + { "xa", AST_PRESENCE_XA}, + { "chat", AST_PRESENCE_CHAT}, + { "dnd", AST_PRESENCE_DND}, +}; + +/*! \brief Flag for the queue */ +static ast_cond_t change_pending; + +struct state_change { + AST_LIST_ENTRY(state_change) list; + char provider[1]; +}; + +/*! \brief A presence state provider */ +struct presence_state_provider { + char label[40]; + ast_presence_state_prov_cb_type callback; + AST_RWLIST_ENTRY(presence_state_provider) list; +}; + +/*! \brief A list of providers */ +static AST_RWLIST_HEAD_STATIC(presence_state_providers, presence_state_provider); + +/*! \brief The state change queue. State changes are queued + for processing by a separate thread */ +static AST_LIST_HEAD_STATIC(state_changes, state_change); + +/*! \brief The presence state change notification thread */ +static pthread_t change_thread = AST_PTHREADT_NULL; + +const char *ast_presence_state2str(enum ast_presence_state state) +{ + int i; + for (i = 0; i < ARRAY_LEN(state2string); i++) { + if (state == state2string[i].state) { + return state2string[i].string; + } + } + return ""; +} + +enum ast_presence_state ast_presence_state_val(const char *val) +{ + int i; + for (i = 0; i < ARRAY_LEN(state2string); i++) { + if (!strcasecmp(val, state2string[i].string)) { + return state2string[i].state; + } + } + return -1; +} + +static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message) +{ + enum ast_presence_state res = -1; + struct ast_event *event; + const char *_subtype; + const char *_message; + + event = ast_event_get_cached(AST_EVENT_PRESENCE_STATE, + AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, presence_provider, + AST_EVENT_IE_END); + + if (!event) { + return res; + } + + res = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE); + _subtype = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE); + _message = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE); + + *subtype = !ast_strlen_zero(_subtype) ? ast_strdup(_subtype) : NULL; + *message = !ast_strlen_zero(_message) ? ast_strdup(_message) : NULL; + ast_event_destroy(event); + + return res; +} + +static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache) +{ + struct presence_state_provider *provider; + char *address; + char *label = ast_strdupa(presence_provider); + int res = -1; + + if (check_cache) { + res = presence_state_cached(presence_provider, subtype, message); + if (res > 0) { + return res; + } + } + + if ((address = strchr(label, ':'))) { + *address = '\0'; + address++; + } else { + ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", presence_provider); + return res; + } + + AST_RWLIST_RDLOCK(&presence_state_providers); + AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) { + ast_debug(5, "Checking provider %s with %s\n", provider->label, label); + + if (!strcasecmp(provider->label, label)) { + res = provider->callback(address, subtype, message); + break; + } + } + AST_RWLIST_UNLOCK(&presence_state_providers); + + + return res; +} + +enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message) +{ + return ast_presence_state_helper(presence_provider, subtype, message, 1); +} + +int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback) +{ + struct presence_state_provider *provider; + + if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) { + return -1; + } + + provider->callback = callback; + ast_copy_string(provider->label, label, sizeof(provider->label)); + + AST_RWLIST_WRLOCK(&presence_state_providers); + AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list); + AST_RWLIST_UNLOCK(&presence_state_providers); + + return 0; +} +int ast_presence_state_prov_del(const char *label) +{ + struct presence_state_provider *provider; + int res = -1; + + AST_RWLIST_WRLOCK(&presence_state_providers); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) { + if (!strcasecmp(provider->label, label)) { + AST_RWLIST_REMOVE_CURRENT(list); + ast_free(provider); + res = 0; + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + AST_RWLIST_UNLOCK(&presence_state_providers); + + return res; +} + +static void do_presence_state_change(const char *provider) +{ + struct ast_event *event; + enum ast_event_type event_type; + char *subtype = NULL; + char *message = NULL; + int state; + + state = ast_presence_state_helper(provider, &subtype, &message, 0); + + if (state < 0) { + return; + } + + event_type = AST_EVENT_PRESENCE_STATE; + + if (!(event = ast_event_new(event_type, + AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, provider, + AST_EVENT_IE_PRESENCE_STATE, AST_EVENT_IE_PLTYPE_UINT, state, + AST_EVENT_IE_PRESENCE_SUBTYPE, AST_EVENT_IE_PLTYPE_STR, S_OR(subtype, ""), + AST_EVENT_IE_PRESENCE_MESSAGE, AST_EVENT_IE_PLTYPE_STR, S_OR(message, ""), + AST_EVENT_IE_END))) { + return; + } + + ast_event_queue_and_cache(event); + ast_free(subtype); + ast_free(message); +} + +int ast_presence_state_changed(const char *presence_provider) +{ + struct state_change *change; + + if ((change_thread == AST_PTHREADT_NULL) || + !(change = ast_calloc(1, sizeof(*change) + strlen(presence_provider)))) { + do_presence_state_change(presence_provider); + } else { + strcpy(change->provider, presence_provider); + AST_LIST_LOCK(&state_changes); + AST_LIST_INSERT_TAIL(&state_changes, change, list); + ast_cond_signal(&change_pending); + AST_LIST_UNLOCK(&state_changes); + } + return 0; +} + +/*! \brief Go through the presence state change queue and update changes in the presence state thread */ +static void *do_presence_changes(void *data) +{ + struct state_change *next, *current; + + for (;;) { + /* This basically pops off any state change entries, resets the list back to NULL, unlocks, and processes each state change */ + AST_LIST_LOCK(&state_changes); + if (AST_LIST_EMPTY(&state_changes)) + ast_cond_wait(&change_pending, &state_changes.lock); + next = AST_LIST_FIRST(&state_changes); + AST_LIST_HEAD_INIT_NOLOCK(&state_changes); + AST_LIST_UNLOCK(&state_changes); + + /* Process each state change */ + while ((current = next)) { + next = AST_LIST_NEXT(current, list); + do_presence_state_change(current->provider); + ast_free(current); + } + } + + return NULL; +} + +int ast_presence_state_engine_init(void) +{ + ast_cond_init(&change_pending, NULL); + if (ast_pthread_create_background(&change_thread, NULL, do_presence_changes, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start presence state change thread.\n"); + return -1; + } + + return 0; +} +