diff options
Diffstat (limited to 'main')
-rw-r--r-- | main/app.c | 70 | ||||
-rw-r--r-- | main/asterisk.c | 6 | ||||
-rw-r--r-- | main/callerid.c | 1 | ||||
-rw-r--r-- | main/channel.c | 1 | ||||
-rw-r--r-- | main/config.c | 123 | ||||
-rw-r--r-- | main/event.c | 22 | ||||
-rw-r--r-- | main/features.c | 54 | ||||
-rw-r--r-- | main/file.c | 44 | ||||
-rw-r--r-- | main/manager.c | 66 | ||||
-rw-r--r-- | main/message.c | 191 | ||||
-rw-r--r-- | main/pbx.c | 388 | ||||
-rw-r--r-- | main/presencestate.c | 317 |
12 files changed, 1249 insertions, 34 deletions
diff --git a/main/app.c b/main/app.c index 3d2fa52c3..31484a18d 100644 --- a/main/app.c +++ b/main/app.c @@ -423,18 +423,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) @@ -444,6 +447,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) @@ -459,6 +463,28 @@ 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) { @@ -709,10 +735,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; @@ -784,7 +816,11 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file, ast_seekstream(ast_channel_stream(chan), 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) { @@ -848,6 +884,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 849cbdd80..6122028bf 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" @@ -4028,6 +4029,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/callerid.c b/main/callerid.c index dc3a91093..37edd992c 100644 --- a/main/callerid.c +++ b/main/callerid.c @@ -1203,6 +1203,7 @@ static const struct ast_value_translation redirecting_reason_types[] = { { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out_of_order", "Called DTE Out-Of-Order" }, { AST_REDIRECTING_REASON_AWAY, "away", "Callee is Away" }, { AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte", "Call Forwarding By The Called DTE" }, + { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm", "Call is being redirected to user's voicemail"}, /* *INDENT-ON* */ }; diff --git a/main/channel.c b/main/channel.c index 8bd973501..320947da7 100644 --- a/main/channel.c +++ b/main/channel.c @@ -2514,6 +2514,7 @@ void ast_channel_clear_softhangup(struct ast_channel *chan, int flag) int ast_softhangup_nolock(struct ast_channel *chan, int cause) { ast_debug(1, "Soft-Hanging up channel '%s'\n", ast_channel_name(chan)); + ast_backtrace(); /* Inform channel driver that we need to be hung up, if it cares */ ast_channel_softhangup_internal_flag_add(chan, cause); ast_queue_frame(chan, &ast_null_frame); diff --git a/main/config.c b/main/config.c index 9d9eefc25..127d89bb1 100644 --- a/main/config.c +++ b/main/config.c @@ -64,6 +64,8 @@ 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 { @@ -2278,6 +2280,39 @@ 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]; @@ -2310,10 +2345,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; } @@ -2968,3 +3005,85 @@ int register_config_cli(void) ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config)); return 0; } + +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; +} diff --git a/main/event.c b/main/event.c index 9ecf71028..d79b07a89 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, }, + }, + }; /*! @@ -1587,6 +1593,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 8b69d1385..04bc5326a 100644 --- a/main/features.c +++ b/main/features.c @@ -409,6 +409,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Bridge together two channels already in the PBX.</para> </description> </manager> + <manager name="Parkinglots" language="en_US"> + <synopsis> + Get a list of parking lots + </synopsis> + <syntax> + <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> + </syntax> + <description> + <para>List all parking lots as a series of AMI events</para> + </description> + </manager> <function name="FEATURE" language="en_US"> <synopsis> Get or set a feature option on a channel. @@ -7347,7 +7358,42 @@ 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 * \param m @@ -7363,6 +7409,7 @@ static int manager_parking_status(struct mansession *s, const struct message *m) struct ao2_iterator iter; struct ast_parkinglot *curlot; int numparked = 0; + long now = time(NULL); if (!ast_strlen_zero(id)) snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); @@ -7379,6 +7426,7 @@ static int manager_parking_status(struct mansession *s, const struct message *m) "Channel: %s\r\n" "From: %s\r\n" "Timeout: %ld\r\n" + "Duration: %ld\r\n" "CallerIDNum: %s\r\n" "CallerIDName: %s\r\n" "ConnectedLineNum: %s\r\n" @@ -7387,7 +7435,8 @@ static int manager_parking_status(struct mansession *s, const struct message *m) "\r\n", curlot->name, cur->parkingnum, ast_channel_name(cur->chan), cur->peername, - (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL), + (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now, + now - (long) cur->start.tv_sec, S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */ S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""), S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */ @@ -8669,6 +8718,7 @@ int ast_features_init(void) res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL); if (!res) { ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status); + ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list); ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park); ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge); } diff --git a/main/file.c b/main/file.c index 52f5af58d..076b31dd7 100644 --- a/main/file.c +++ b/main/file.c @@ -1237,11 +1237,18 @@ 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; if (!breakon) @@ -1257,6 +1264,11 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, if (ast_test_flag(ast_channel_flags(c), AST_FLAG_MASQ_NOSTREAM)) orig_chan_name = ast_strdupa(ast_channel_name(c)); + if (ast_channel_stream(c) && cb) { + long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000); + cb(c, ms_len, AST_WAITSTREAM_CB_START); + } + while (ast_channel_stream(c)) { int res; int ms; @@ -1318,6 +1330,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; @@ -1328,13 +1341,19 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, } else { ungetc(eoftest, ast_channel_stream(c)->f); } + cb_val = AST_WAITSTREAM_CB_FASTFORWARD; } else if (strchr(reverse, res)) { ast_stream_rewind(ast_channel_stream(c), skip_ms); + cb_val = AST_WAITSTREAM_CB_REWIND; } else if (strchr(breakon, res)) { ast_frfree(fr); ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY); return res; } + if (cb_val && cb) { + long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000); + cb(c, ms_len, cb_val); + } } break; case AST_FRAME_CONTROL: @@ -1385,21 +1404,32 @@ static int waitstream_core(struct ast_channel *c, const char *breakon, return (err || ast_channel_softhangup_internal_flag(c)) ? -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) @@ -1410,7 +1440,7 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context) if (!context) context = ast_channel_context(c); 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 2861c6d9b..4bf859e2e 100644 --- a/main/manager.c +++ b/main/manager.c @@ -82,6 +82,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/security_events.h" #include "asterisk/aoc.h" #include "asterisk/stringfields.h" +#include "asterisk/presencestate.h" /*** DOCUMENTATION <manager name="Ping" language="en_US"> @@ -510,6 +511,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") the hint for the extension and the status.</para> </description> </manager> + <manager name="PresenceState" language="en_US"> + <synopsis> + Check Presence State + </synopsis> + <syntax> + <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> + <parameter name="Provider" required="true"> + <para>Presence Provider to check the state of</para> + </parameter> + </syntax> + <description> + <para>Report the presence state for the given presence provider.</para> + <para>Will return a <literal>Presence State</literal> message. The response will include the + presence state and, if set, a presence subtype and custom message.</para> + </description> + </manager> <manager name="AbsoluteTimeout" language="en_US"> <synopsis> Set absolute timeout. @@ -1218,6 +1235,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" }, }; @@ -3211,6 +3229,7 @@ static int action_hangup(struct mansession *s, const struct message *m) if (name_or_regex[0] != '/') { if (!(c = ast_channel_get_by_name(name_or_regex))) { + ast_log(LOG_NOTICE, "!!!!!!!!!! Can't find channel to hang up!\n"); astman_send_error(s, m, "No such channel"); return 0; } @@ -4384,6 +4403,43 @@ static int action_extensionstate(struct mansession *s, const struct message *m) return 0; } +static int action_presencestate(struct mansession *s, const struct message *m) +{ + const char *provider = astman_get_header(m, "Provider"); + enum ast_presence_state state; + char *subtype; + char *message; + char subtype_header[256] = ""; + char message_header[256] = ""; + + if (ast_strlen_zero(provider)) { + astman_send_error(s, m, "No provider specified"); + return 0; + } + + state = ast_presence_state(provider, &subtype, &message); + + if (!ast_strlen_zero(subtype)) { + snprintf(subtype_header, sizeof(subtype_header), + "Subtype: %s\r\n", subtype); + } + + if (!ast_strlen_zero(message)) { + snprintf(message_header, sizeof(message_header), + "Message: %s\r\n", message); + } + + astman_append(s, "Message: Presence State\r\n" + "State: %s\r\n" + "%s" + "%s" + "\r\n", + ast_presence_state2str(state), + subtype_header, + message_header); + return 0; +} + static int action_timeout(struct mansession *s, const struct message *m) { struct ast_channel *c; @@ -5485,10 +5541,17 @@ int ast_manager_unregister(const 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); @@ -6850,6 +6913,7 @@ static int __init_manager(int reload) ast_manager_register_xml_core("Originate", EVENT_FLAG_ORIGINATE, action_originate); ast_manager_register_xml_core("Command", EVENT_FLAG_COMMAND, action_command); ast_manager_register_xml_core("ExtensionState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstate); + ast_manager_register_xml_core("PresenceState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_presencestate); ast_manager_register_xml_core("AbsoluteTimeout", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, action_timeout); ast_manager_register_xml_core("MailboxStatus", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxstatus); ast_manager_register_xml_core("MailboxCount", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxcount); diff --git a/main/message.c b/main/message.c index 5722969d0..e26b8c375 100644 --- a/main/message.c +++ b/main/message.c @@ -32,6 +32,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/module.h" #include "asterisk/datastore.h" #include "asterisk/pbx.h" +#include "asterisk/manager.h" #include "asterisk/strings.h" #include "asterisk/astobj2.h" #include "asterisk/app.h" @@ -56,6 +57,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Read-only. The source of the message. When processing an incoming message, this will be set to the source of the message.</para> </enum> + <enum name="custom_data"> + <para>Write-only. Mark or unmark all message headers for an outgoing + message. The following values can be set:</para> + <enumlist> + <enum name="mark_all_outbound"> + <para>Mark all headers for an outgoing message.</para> + </enum> + <enum name="clear_all_outbound"> + <para>Unmark all headers for an outgoing message.</para> + </enum> + </enumlist> + </enum> <enum name="body"> <para>Read/Write. The message body. When processing an incoming message, this includes the body of the message that Asterisk @@ -139,6 +152,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </variablelist> </description> </application> + <manager name="MessageSend" language="en_US"> + <synopsis> + Send an out of call message to an endpoint. + </synopsis> + <syntax> + <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> + <parameter name="To" required="true"> + <para>The URI the message is to be sent to.</para> + </parameter> + <parameter name="From"> + <para>A From URI for the message if needed for the + message technology being used to send this message.</para> + <note> + <para>For SIP the from parameter can be a configured peer name + or in the form of "display-name" <URI>.</para> + </note> + </parameter> + <parameter name="Body"> + <para>The message body text. This must not contain any newlines as that + conflicts with the AMI protocol.</para> + </parameter> + <parameter name="Base64Body"> + <para>Text bodies requiring the use of newlines have to be base64 encoded + in this field. Base64Body will be decoded before being sent out. + Base64Body takes precedence over Body.</para> + </parameter> + <parameter name="Variable"> + <para>Message variable to set, multiple Variable: headers are + allowed. The header value is a comma separated list of + name=value pairs.</para> + </parameter> + </syntax> + </manager> ***/ struct msg_data { @@ -393,6 +439,12 @@ struct ast_msg *ast_msg_alloc(void) return msg; } +struct ast_msg *ast_msg_ref(struct ast_msg *msg) +{ + ao2_ref(msg, 1); + return msg; +} + struct ast_msg *ast_msg_destroy(struct ast_msg *msg) { ao2_ref(msg, -1); @@ -519,7 +571,7 @@ static int msg_set_var_full(struct ast_msg *msg, const char *name, const char *v return 0; } -static int msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value) +int ast_msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value) { return msg_set_var_full(msg, name, value, 1); } @@ -850,6 +902,26 @@ static int msg_func_write(struct ast_channel *chan, const char *function, ast_msg_set_from(msg, "%s", value); } else if (!strcasecmp(data, "body")) { ast_msg_set_body(msg, "%s", value); + } else if (!strcasecmp(data, "custom_data")) { + int outbound = -1; + if (!strcasecmp(value, "mark_all_outbound")) { + outbound = 1; + } else if (!strcasecmp(value, "clear_all_outbound")) { + outbound = 0; + } else { + ast_log(LOG_WARNING, "'%s' is not a valid value for custom_data\n", value); + } + + if (outbound != -1) { + struct msg_data *hdr_data; + struct ao2_iterator iter = ao2_iterator_init(msg->vars, 0); + + while ((hdr_data = ao2_iterator_next(&iter))) { + hdr_data->send = outbound; + ao2_ref(hdr_data, -1); + } + ao2_iterator_destroy(&iter); + } } else { ast_log(LOG_WARNING, "'%s' is not a valid write argument.\n", data); } @@ -910,7 +982,7 @@ static int msg_data_func_write(struct ast_channel *chan, const char *function, ao2_lock(msg); - msg_set_var_outbound(msg, data, value); + ast_msg_set_var_outbound(msg, data, value); ao2_unlock(msg); ao2_ref(msg, -1); @@ -1041,6 +1113,120 @@ exit_cleanup: return 0; } +static int action_messagesend(struct mansession *s, const struct message *m) +{ + const char *to = ast_strdupa(astman_get_header(m, "To")); + const char *from = astman_get_header(m, "From"); + const char *body = astman_get_header(m, "Body"); + const char *base64body = astman_get_header(m, "Base64Body"); + char base64decoded[1301] = { 0, }; + char *tech_name = NULL; + struct ast_variable *vars = NULL; + struct ast_variable *data = NULL; + struct ast_msg_tech_holder *tech_holder = NULL; + struct ast_msg *msg; + int res = -1; + + if (ast_strlen_zero(to)) { + astman_send_error(s, m, "No 'To' address specified."); + return -1; + } + + if (!ast_strlen_zero(base64body)) { + ast_base64decode((unsigned char *) base64decoded, base64body, sizeof(base64decoded) - 1); + body = base64decoded; + } + + tech_name = ast_strdupa(to); + tech_name = strsep(&tech_name, ":"); + { + struct ast_msg_tech tmp_msg_tech = { + .name = tech_name, + }; + struct ast_msg_tech_holder tmp_tech_holder = { + .tech = &tmp_msg_tech, + }; + + tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER); + } + + if (!tech_holder) { + astman_send_error(s, m, "Message technology not found."); + return -1; + } + + if (!(msg = ast_msg_alloc())) { + ao2_ref(tech_holder, -1); + astman_send_error(s, m, "Internal failure\n"); + return -1; + } + + data = astman_get_variables(m); + for (vars = data; vars; vars = vars->next) { + ast_msg_set_var_outbound(msg, vars->name, vars->value); + } + + ast_msg_set_body(msg, "%s", body); + + ast_rwlock_rdlock(&tech_holder->tech_lock); + if (tech_holder->tech) { + res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, "")); + } + ast_rwlock_unlock(&tech_holder->tech_lock); + + ast_variables_destroy(vars); + ao2_ref(tech_holder, -1); + ao2_ref(msg, -1); + + if (res) { + astman_send_error(s, m, "Message failed to send."); + } else { + astman_send_ack(s, m, "Message successfully sent"); + } + return res; +} + +int ast_msg_send(struct ast_msg *msg, const char *to, const char *from) +{ + char *tech_name = NULL; + struct ast_msg_tech_holder *tech_holder = NULL; + int res = -1; + + if (ast_strlen_zero(to)) { + ao2_ref(msg, -1); + return -1; + } + + tech_name = ast_strdupa(to); + tech_name = strsep(&tech_name, ":"); + { + struct ast_msg_tech tmp_msg_tech = { + .name = tech_name, + }; + struct ast_msg_tech_holder tmp_tech_holder = { + .tech = &tmp_msg_tech, + }; + + tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER); + } + + if (!tech_holder) { + ao2_ref(msg, -1); + return -1; + } + + ast_rwlock_rdlock(&tech_holder->tech_lock); + if (tech_holder->tech) { + res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, "")); + } + ast_rwlock_unlock(&tech_holder->tech_lock); + + ao2_ref(tech_holder, -1); + ao2_ref(msg, -1); + + return res; +} + int ast_msg_tech_register(const struct ast_msg_tech *tech) { struct ast_msg_tech_holder tmp_tech_holder = { @@ -1125,6 +1311,7 @@ int ast_msg_init(void) res = __ast_custom_function_register(&msg_function, NULL); res |= __ast_custom_function_register(&msg_data_function, NULL); res |= ast_register_application2(app_msg_send, msg_send_exec, NULL, NULL, NULL); + res |= ast_manager_register_xml_core("MessageSend", EVENT_FLAG_MESSAGE, action_messagesend); return res; } diff --git a/main/pbx.c b/main/pbx.c index bdaea7288..3ed7df4ed 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. */ }; @@ -1024,6 +1033,7 @@ static int remove_hintdevice(struct ast_hint *hint) return 0; } +static char *parse_hint_device(struct ast_str *hint_args); /*! * \internal * \brief Destroy the given hintdevice object. @@ -1060,7 +1070,7 @@ static int add_hintdevice(struct ast_hint *hint, const char *devicelist) return -1; } ast_str_set(&str, 0, "%s", devicelist); - parse = ast_str_buffer(str); + parse = parse_hint_device(str); while ((cur = strsep(&parse, "&"))) { devicelength = strlen(cur); @@ -1094,6 +1104,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 +1281,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; @@ -4474,6 +4493,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; @@ -4481,7 +4536,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, "&"))) { @@ -4539,6 +4594,209 @@ 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; @@ -4626,14 +4884,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); } @@ -4805,7 +5073,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; @@ -4840,14 +5107,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 */ @@ -4890,6 +5164,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; @@ -4913,6 +5190,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); @@ -7432,6 +7715,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]; }; @@ -7633,6 +7920,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); } @@ -7686,8 +7980,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); } } @@ -7702,11 +8003,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); } @@ -10469,6 +10776,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; @@ -10483,7 +10835,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); } } @@ -10515,6 +10867,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)) { @@ -10541,7 +10896,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"); } @@ -10568,6 +10923,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 000000000..df64dab24 --- /dev/null +++ b/main/presencestate.c @@ -0,0 +1,317 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011-2012, Digium, Inc. + * + * David Vossel <dvossel@digium.com> + * + * 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 AST_PRESENCE_INVALID; +} + +static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message) +{ + enum ast_presence_state res = AST_PRESENCE_INVALID; + 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 = AST_PRESENCE_INVALID; + + if (check_cache) { + res = presence_state_cached(presence_provider, subtype, message); + if (res != AST_PRESENCE_INVALID) { + 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); +} + +enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message) +{ + return ast_presence_state_helper(presence_provider, subtype, message, 0); +} + +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 presence_state_event(const char *provider, + enum ast_presence_state state, + const char *subtype, + const char *message) +{ + struct ast_event *event; + + if (!(event = ast_event_new(AST_EVENT_PRESENCE_STATE, + 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); +} + +static void do_presence_state_change(const char *provider) +{ + char *subtype = NULL; + char *message = NULL; + enum ast_presence_state state; + + state = ast_presence_state_helper(provider, &subtype, &message, 0); + + if (state < 0) { + return; + } + + presence_state_event(provider, state, subtype, message); + ast_free(subtype); + ast_free(message); +} + +int ast_presence_state_changed_literal(enum ast_presence_state state, + const char *subtype, + const char *message, + const char *presence_provider) +{ + struct state_change *change; + + if (state != AST_PRESENCE_NOT_SET) { + presence_state_event(presence_provider, state, subtype, message); + } else 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; +} + +int ast_presence_state_changed(enum ast_presence_state state, + const char *subtype, + const char *message, + const char *fmt, ...) +{ + char buf[AST_MAX_EXTENSION]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + return ast_presence_state_changed_literal(state, subtype, message, buf); +} + +/*! \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; +} + |