diff options
-rw-r--r-- | CHANGES | 8 | ||||
-rw-r--r-- | apps/app_queue.c | 1061 | ||||
-rw-r--r-- | include/asterisk/app.h | 16 | ||||
-rw-r--r-- | include/asterisk/bridge_basic.h | 12 | ||||
-rw-r--r-- | include/asterisk/core_unreal.h | 17 | ||||
-rw-r--r-- | include/asterisk/features.h | 14 | ||||
-rw-r--r-- | main/app.c | 28 | ||||
-rw-r--r-- | main/bridge.c | 23 | ||||
-rw-r--r-- | main/bridge_basic.c | 32 | ||||
-rw-r--r-- | main/core_local.c | 130 | ||||
-rw-r--r-- | main/features.c | 33 |
11 files changed, 1036 insertions, 338 deletions
@@ -98,6 +98,14 @@ Queue AgentConnect, AgentComplete, AgentDump, and AgentRingNoAnswer will always be sent. The "Variable" fields will also no longer exist on the Agent* events. + * The queue log now differentiates between blind and attended transfers. A + blind transfer will result in a BLINDTRANSFER message with the destination + context and extension. An attended transfer will result in an + ATTENDEDTRANSFER message. This message will indicate the method by which + the attended transfer was completed: "BRIDGE" for a bridge merge, "APP" + for running an application on a bridge or channel, or "LINK" for linking + two bridges together with local channels. + * Queues now support a hint for member paused state. The hint uses the form 'Queue:{queue_name}_pause_{member_name}', where {queue_name} and {member_name} are the name of the queue and the name of the member to subscribe to, diff --git a/apps/app_queue.c b/apps/app_queue.c index 30b5ed670..bd7958767 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -108,6 +108,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_channels.h" #include "asterisk/stasis_message_router.h" #include "asterisk/bridge_after.h" +#include "asterisk/stasis_bridges.h" +#include "asterisk/core_local.h" +#include "asterisk/mixmonitor.h" +#include "asterisk/core_unreal.h" +#include "asterisk/bridge_basic.h" /* Define, to debug reference counts on queues, without debugging reference counts on queue members */ /* #define REF_DEBUG_ONLY_QUEUES */ @@ -1584,10 +1589,6 @@ static void update_realtime_members(struct call_queue *q); static struct member *interface_exists(struct call_queue *q, const char *interface); static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused); -#if 0 // BUGBUG -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); -#endif // BUGBUG - static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface); /*! \brief sets the QUEUESTATUS channel variable */ static void set_queue_result(struct ast_channel *chan, enum queue_result res) @@ -1734,7 +1735,9 @@ static inline struct call_queue *_queue_ref(struct call_queue *q, const char *ta static inline struct call_queue *_queue_unref(struct call_queue *q, const char *tag, const char *file, int line, const char *filename) { - __ao2_ref_debug(q, -1, tag, file, line, filename); + if (q) { + __ao2_ref_debug(q, -1, tag, file, line, filename); + } return NULL; } @@ -1753,7 +1756,9 @@ static inline struct call_queue *queue_ref(struct call_queue *q) static inline struct call_queue *queue_unref(struct call_queue *q) { - ao2_ref(q, -1); + if (q) { + ao2_ref(q, -1); + } return NULL; } #endif @@ -1906,25 +1911,19 @@ static void queue_member_manager_event(void *data, "%s", ast_str_buffer(event_string)); } -static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct ast_channel *agent, struct stasis_message_type *type, struct ast_json *blob) +static void queue_publish_multi_channel_snapshot_blob(struct stasis_topic *topic, + struct ast_channel_snapshot *caller_snapshot, + struct ast_channel_snapshot *agent_snapshot, + struct stasis_message_type *type, struct ast_json *blob) { RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); - struct ast_channel_snapshot *caller_snapshot; - struct ast_channel_snapshot *agent_snapshot; payload = ast_multi_channel_blob_create(blob); if (!payload) { return; } - caller_snapshot = ast_channel_snapshot_create(caller); - agent_snapshot = ast_channel_snapshot_create(agent); - - if (!caller_snapshot || !agent_snapshot) { - return; - } - ast_multi_channel_blob_add_channel(payload, "caller", caller_snapshot); ast_multi_channel_blob_add_channel(payload, "agent", agent_snapshot); @@ -1933,7 +1932,24 @@ static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct return; } - stasis_publish(ast_channel_topic(caller), msg); + stasis_publish(topic, msg); +} + +static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct ast_channel *agent, + struct stasis_message_type *type, struct ast_json *blob) +{ + RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, agent_snapshot, NULL, ao2_cleanup); + + caller_snapshot = ast_channel_snapshot_create(caller); + agent_snapshot = ast_channel_snapshot_create(agent); + + if (!caller_snapshot || !agent_snapshot) { + return; + } + + queue_publish_multi_channel_snapshot_blob(ast_channel_topic(caller), caller_snapshot, + agent_snapshot, type, blob); } static void queue_publish_member_blob(struct stasis_message_type *type, struct ast_json *blob) @@ -3927,7 +3943,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies member_call_pending_clear(tmp->member); - /* BUGBUG: Raise a BUSY dial end message here */ + publish_dial_end_event(qe->chan, tmp, NULL, "BUSY"); tmp->stillgoing = 0; ++*busies; return 0; @@ -4350,14 +4366,8 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte if (pos == 1 /* not found */) { if (numlines == (numbusies + numnochan)) { ast_debug(1, "Everyone is busy at this time\n"); - /* BUGBUG: We shouldn't have to set anything here, as each - * individual dial attempt should have set that CDR to busy - */ } else { ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan); - /* BUGBUG: We shouldn't have to set anything here, as each - * individual dial attempt should have set that CDR to busy - */ } *to = 0; return NULL; @@ -4983,7 +4993,6 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r return res; } -#if 0 // BUGBUG /*! * \brief update the queue status * \retval Always 0 @@ -5028,7 +5037,6 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom ao2_unlock(q); return 0; } -#endif // BUGBUG /*! \brief Calculate the metric of each member in the outgoing callattempts * @@ -5117,15 +5125,17 @@ enum agent_complete_reason { TRANSFER }; -#if 0 // BUGBUG /*! \brief Send out AMI message with member call completion status information */ -static void send_agent_complete(const struct queue_ent *qe, const char *queuename, - const struct ast_channel *peer, const struct member *member, time_t callstart, - char *vars, size_t vars_len, enum agent_complete_reason rsn) +static void send_agent_complete(const char *queuename, struct ast_channel_snapshot *caller, + struct ast_channel_snapshot *peer, const struct member *member, time_t holdstart, + time_t callstart, enum agent_complete_reason rsn) { const char *reason = NULL; /* silence dumb compilers */ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_assert(peer != NULL); + ast_assert(caller != NULL); + switch (rsn) { case CALLER: reason = "caller"; @@ -5142,121 +5152,656 @@ static void send_agent_complete(const struct queue_ent *qe, const char *queuenam "Queue", queuename, "Interface", member->interface, "MemberName", member->membername, - "HoldTime", (long)(callstart - qe->start) - "TalkTime", (long)(time(NULL) - callstart) + "HoldTime", (long)(callstart - holdstart), + "TalkTime", (long)(time(NULL) - callstart), "Reason", reason); - queue_publish_multi_channel_blob(qe->chan, peer, queue_agent_complete_type(), blob); + + queue_publish_multi_channel_snapshot_blob(ast_queue_topic(queuename), caller, peer, + queue_agent_complete_type(), blob); } -#endif // BUGBUG -#if 0 // BUGBUG -struct queue_transfer_ds { - struct queue_ent *qe; +static void queue_agent_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + struct ast_channel_blob *agent_blob; + + agent_blob = stasis_message_data(msg); + + if (ast_channel_agent_login_type() == stasis_message_type(msg)) { + ast_queue_log("NONE", agent_blob->snapshot->uniqueid, + ast_json_string_get(ast_json_object_get(agent_blob->blob, "agent")), + "AGENTLOGIN", "%s", agent_blob->snapshot->name); + } else if (ast_channel_agent_logoff_type() == stasis_message_type(msg)) { + ast_queue_log("NONE", agent_blob->snapshot->uniqueid, + ast_json_string_get(ast_json_object_get(agent_blob->blob, "agent")), + "AGENTLOGOFF", "%s|%ld", agent_blob->snapshot->name, + (long) ast_json_integer_get(ast_json_object_get(agent_blob->blob, "logintime"))); + } +} + +/*! + * \brief Structure representing relevant data during a local channel optimization + * + * The reason we care about local channel optimizations is that we want to be able + * to accurately report when the caller and queue member have stopped talking to + * each other. A local channel optimization can cause it to appear that the conversation + * has stopped immediately after it has begun. By tracking that the relevant channels + * to monitor have changed due to a local channel optimization, we can give accurate + * reports. + * + * Local channel optimizations for queues are restricted from their normal operation. + * Bridges created by queues can only be the destination of local channel optimizations, + * not the source. In addition, move-swap local channel optimizations are the only + * permitted types of local channel optimization. + * + * This data is populated when we are told that a local channel optimization begin + * is occurring. When we get told the optimization has ended successfully, we then + * apply the data here into the queue_stasis_data. + */ +struct local_optimization { + /*! The uniqueid of the channel that will be taking the place of the caller or member */ + const char *source_chan_uniqueid; + /*! Indication of whether we think there is a local channel optimization in progress */ + int in_progress; + /*! The identifier for this local channel optimization */ + unsigned int id; +}; + +/*! + * \brief User data for stasis subscriptions used for queue calls. + * + * app_queue subscribes to channel and bridge events for all bridged calls. + * app_queue cares about the following events: + * + * \li bridge enter: To determine the unique ID of the bridge created for the call. + * \li blind transfer: To send an appropriate agent complete event. + * \li attended transfer: To send an appropriate agent complete event. + * \li local optimization: To update caller and member unique IDs for the call. + * \li hangup: To send an appropriate agent complete event. + * + * The stasis subscriptions last until we determine that the caller and the member + * are no longer bridged with each other. + */ +struct queue_stasis_data { + AST_DECLARE_STRING_FIELDS( + /*! The unique ID of the caller's channel. */ + AST_STRING_FIELD(caller_uniqueid); + /*! The unique ID of the queue member's channel */ + AST_STRING_FIELD(member_uniqueid); + /*! The unique ID of the bridge created by the queue */ + AST_STRING_FIELD(bridge_uniqueid); + ); + /*! The relevant queue */ + struct call_queue *queue; + /*! The queue member that has answered the call */ struct member *member; + /*! The time at which the caller entered the queue. Start of the caller's hold time */ + time_t holdstart; + /*! The time at which the member answered the call. */ time_t starttime; + /*! The original position of the caller when he entered the queue */ + int caller_pos; + /*! Indication if the call was answered within the configured service level of the queue */ int callcompletedinsl; + /*! Indicates if the stasis subscriptions are shutting down */ + int dying; + /*! The stasis message router for bridge events */ + struct stasis_message_router *bridge_router; + /*! The stasis message router for channel events */ + struct stasis_message_router *channel_router; + /*! Local channel optimization details for the caller */ + struct local_optimization caller_optimize; + /*! Local channel optimization details for the member */ + struct local_optimization member_optimize; }; -#endif // BUGBUG -#if 0 // BUGBUG -static void queue_transfer_destroy(void *data) +/*! + * \internal + * \brief Free memory for a queue_stasis_data + */ +static void queue_stasis_data_destructor(void *obj) { - struct queue_transfer_ds *qtds = data; - ast_free(qtds); + struct queue_stasis_data *queue_data = obj; + + /* This can only happen if refcounts for this object have got severely messed up */ + ast_assert(queue_data->bridge_router == NULL); + ast_assert(queue_data->channel_router == NULL); + + ao2_cleanup(queue_data->member); + queue_unref(queue_data->queue); + ast_string_field_free_memory(queue_data); } -#endif // BUGBUG -#if 0 // BUGBUG -/*! \brief a datastore used to help correctly log attended transfers of queue callers +/*! + * \internal + * \brief End all stasis subscriptions on a queue_stasis_data */ -static const struct ast_datastore_info queue_transfer_info = { - .type = "queue_transfer", - .chan_fixup = queue_transfer_fixup, - .destroy = queue_transfer_destroy, -}; -#endif // BUGBUG +static void remove_stasis_subscriptions(struct queue_stasis_data *queue_data) +{ + SCOPED_AO2LOCK(lock, queue_data); + + queue_data->dying = 1; + stasis_message_router_unsubscribe(queue_data->bridge_router); + queue_data->bridge_router = NULL; + stasis_message_router_unsubscribe(queue_data->channel_router); + queue_data->channel_router = NULL; +} + +/*! + * \internal + * \brief Allocate a queue_stasis_data and initialize its data. + */ +static struct queue_stasis_data *queue_stasis_data_alloc(struct queue_ent *qe, + struct ast_channel *peer, struct member *mem, time_t holdstart, + time_t starttime, int callcompletedinsl) +{ + struct queue_stasis_data *queue_data; + + queue_data = ao2_alloc(sizeof(*queue_data), queue_stasis_data_destructor); + if (!queue_data) { + return NULL; + } + + if (ast_string_field_init(queue_data, 64)) { + ao2_cleanup(queue_data); + return NULL; + } -#if 0 // BUGBUG -/*! \brief Log an attended transfer when a queue caller channel is masqueraded + ast_string_field_set(queue_data, caller_uniqueid, ast_channel_uniqueid(qe->chan)); + ast_string_field_set(queue_data, member_uniqueid, ast_channel_uniqueid(peer)); + queue_data->queue = queue_ref(qe->parent); + queue_data->starttime = starttime; + queue_data->holdstart = holdstart; + queue_data->callcompletedinsl = callcompletedinsl; + queue_data->caller_pos = qe->opos; + ao2_ref(mem, +1); + queue_data->member = mem; + return queue_data; +} + +/*! + * \internal + * \brief Log an attended transfer in the queue log. * - * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when - * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan - * to new_chan. This is why new_chan is referenced for exten, context, and datastore information. + * Attended transfer queue log messages vary based on the method by which the + * attended transfer was completed. * - * At the end of this, we want to remove the datastore so that this fixup function is not called on any - * future masquerades of the caller during the current call. + * \param queue_data Data pertaining to the particular call in the queue. + * \param caller The channel snapshot for the caller channel in the queue. + * \param atxfer_msg The stasis attended transfer message data. */ -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +static void log_attended_transfer(struct queue_stasis_data *queue_data, struct ast_channel_snapshot *caller, + struct ast_attended_transfer_message *atxfer_msg) { - struct queue_transfer_ds *qtds = data; - struct queue_ent *qe = qtds->qe; - struct member *member = qtds->member; - time_t callstart = qtds->starttime; - int callcompletedinsl = qtds->callcompletedinsl; - struct ast_datastore *datastore; + RAII_VAR(struct ast_str *, transfer_str, ast_str_create(32), ast_free); + + if (!transfer_str) { + ast_log(LOG_WARNING, "Unable to log attended transfer to queue log\n"); + return; + } + + switch (atxfer_msg->dest_type) { + case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE: + ast_str_set(&transfer_str, 0, "BRIDGE|%s", atxfer_msg->dest.bridge); + break; + case AST_ATTENDED_TRANSFER_DEST_APP: + ast_str_set(&transfer_str, 0, "APP|%s", atxfer_msg->dest.app); + break; + case AST_ATTENDED_TRANSFER_DEST_LINK: + ast_str_set(&transfer_str, 0, "LINK|%s|%s", atxfer_msg->dest.links[0]->name, + atxfer_msg->dest.links[1]->name); + break; + case AST_ATTENDED_TRANSFER_DEST_THREEWAY: + case AST_ATTENDED_TRANSFER_DEST_FAIL: + /* Threeways are headed off and should not be logged here */ + ast_assert(0); + return; + } + + ast_queue_log(queue_data->queue->name, caller->uniqueid, queue_data->member->membername, "ATTENDEDTRANSFER", "%s|%ld|%ld|%d", + ast_str_buffer(transfer_str), + (long) queue_data->starttime - queue_data->holdstart, + (long) time(NULL) - queue_data->starttime, queue_data->caller_pos); +} - ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), member->membername, "TRANSFER", "%s|%s|%ld|%ld|%d", - ast_channel_exten(new_chan), ast_channel_context(new_chan), (long) (callstart - qe->start), - (long) (time(NULL) - callstart), qe->opos); +/*! + * \internal + * \brief Handle a stasis bridge enter event. + * + * We track this particular event in order to learn what bridge + * was created for the queue call. + * + * \param userdata Data pertaining to the particular call in the queue. + * \param sub The stasis subscription on which the message occurred. + * \param topic The topic for this event. + * \param msg The stasis message for the bridge enter event + */ +static void handle_bridge_enter(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + struct queue_stasis_data *queue_data = userdata; + struct ast_bridge_blob *enter_blob = stasis_message_data(msg); + + if (queue_data->dying) { + return; + } + + if (!ast_strlen_zero(queue_data->bridge_uniqueid)) { + return; + } - update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart)); + if (!strcmp(enter_blob->channel->uniqueid, queue_data->caller_uniqueid)) { + ast_string_field_set(queue_data, bridge_uniqueid, + enter_blob->bridge->uniqueid); + ast_debug(3, "Detected entry of caller channel %s into bridge %s\n", + enter_blob->channel->name, queue_data->bridge_uniqueid); + } +} - /* No need to lock the channels because they are already locked in ast_do_masquerade */ - if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) { - ast_channel_datastore_remove(old_chan, datastore); +/*! + * \brief Handle a blind transfer event + * + * This event is important in order to be able to log the end of the + * call to the queue log and to stasis. + * + * \param userdata Data pertaining to the particular call in the queue. + * \param sub The stasis subscription on which the message occurred. + * \param topic The topic for this event. + * \param msg The stasis message for the blind transfer event + */ +static void handle_blind_transfer(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + struct queue_stasis_data *queue_data = userdata; + struct ast_bridge_blob *blind_blob = stasis_message_data(msg); + struct ast_json *result_blob; + struct ast_json *exten_blob; + struct ast_json *context_blob; + const char *exten; + const char *context; + RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, member_snapshot, NULL, ao2_cleanup); + + if (queue_data->dying) { + return; + } + + result_blob = ast_json_object_get(blind_blob->blob, "result"); + if (!result_blob) { + return; + } + + if (ast_json_integer_get(result_blob) == AST_BRIDGE_TRANSFER_FAIL) { + return; + } + + ao2_lock(queue_data); + + if (ast_strlen_zero(queue_data->bridge_uniqueid) || + strcmp(queue_data->bridge_uniqueid, blind_blob->bridge->uniqueid)) { + ao2_unlock(queue_data); + return; + } + + caller_snapshot = ast_channel_snapshot_get_latest(queue_data->caller_uniqueid); + member_snapshot = ast_channel_snapshot_get_latest(queue_data->member_uniqueid); + + ao2_unlock(queue_data); + + exten_blob = ast_json_object_get(blind_blob->blob, "exten"); + exten = exten_blob ? ast_json_string_get(exten_blob) : "<unknown>"; + context_blob = ast_json_object_get(blind_blob->blob, "context"); + context = context_blob ? ast_json_string_get(context_blob) : "<unknown>"; + + ast_debug(3, "Detected blind transfer in queue %s\n", queue_data->queue->name); + ast_queue_log(queue_data->queue->name, caller_snapshot->uniqueid, queue_data->member->membername, + "BLINDTRANSFER", "%s|%s|%ld|%ld|%d", + exten, context, + (long) queue_data->starttime - queue_data->holdstart, + (long) time(NULL) - queue_data->starttime, queue_data->caller_pos); + + send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member, + queue_data->holdstart, queue_data->starttime, TRANSFER); + update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl, + time(NULL) - queue_data->starttime); + remove_stasis_subscriptions(queue_data); +} + +/*! + * \brief Handle an attended transfer event + * + * This event is important in order to be able to log the end of the + * call to the queue log and to stasis. + * + * \param userdata Data pertaining to the particular call in the queue. + * \param sub The stasis subscription on which the message occurred. + * \param topic The topic for this event. + * \param msg The stasis message for the attended transfer event. + */ +static void handle_attended_transfer(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + struct queue_stasis_data *queue_data = userdata; + struct ast_attended_transfer_message *atxfer_msg = stasis_message_data(msg); + RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, member_snapshot, NULL, ao2_cleanup); + + if (queue_data->dying) { + return; + } + + if (atxfer_msg->result == AST_BRIDGE_TRANSFER_FAIL || + atxfer_msg->dest_type == AST_ATTENDED_TRANSFER_DEST_THREEWAY) { + return; + } + + ao2_lock(queue_data); + + if (ast_strlen_zero(queue_data->bridge_uniqueid)) { + ao2_unlock(queue_data); + return; + } + + if ((!atxfer_msg->to_transferee.bridge_snapshot || strcmp(queue_data->bridge_uniqueid, + atxfer_msg->to_transferee.bridge_snapshot->uniqueid)) && + (!atxfer_msg->to_transfer_target.bridge_snapshot || strcmp(queue_data->bridge_uniqueid, + atxfer_msg->to_transfer_target.bridge_snapshot->uniqueid))) { + ao2_unlock(queue_data); + return; + } + + caller_snapshot = ast_channel_snapshot_get_latest(queue_data->caller_uniqueid); + member_snapshot = ast_channel_snapshot_get_latest(queue_data->member_uniqueid); + + ao2_unlock(queue_data); + + ast_debug(3, "Detected attended transfer in queue %s\n", queue_data->queue->name); + + log_attended_transfer(queue_data, caller_snapshot, atxfer_msg); + + send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member, + queue_data->holdstart, queue_data->starttime, TRANSFER); + update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl, + time(NULL) - queue_data->starttime); + remove_stasis_subscriptions(queue_data); +} + +/*! + * \internal + * \brief Callback for all stasis bridge events + * + * Based on the event and what bridge it is on, the task is farmed out to relevant + * subroutines for further processing. + */ +static void queue_bridge_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + if (stasis_subscription_final_message(sub, msg)) { + ao2_cleanup(userdata); + } +} + +/*! + * \internal + * \brief Handler for the beginning of a local channel optimization + * + * This method gathers data relevant to the local channel optimization and stores + * it to be used once the local optimization completes. + * + * \param userdata Data pertaining to the particular call in the queue. + * \param sub The stasis subscription on which the message occurred. + * \param topic The topic for this event. + * \param msg The stasis message for the local optimization begin event + */ +static void handle_local_optimization_begin(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + struct queue_stasis_data *queue_data = userdata; + struct ast_multi_channel_blob *optimization_blob = stasis_message_data(msg); + struct ast_channel_snapshot *local_one = ast_multi_channel_blob_get_channel(optimization_blob, "1"); + struct ast_channel_snapshot *local_two = ast_multi_channel_blob_get_channel(optimization_blob, "2"); + struct ast_channel_snapshot *source = ast_multi_channel_blob_get_channel(optimization_blob, "source"); + struct local_optimization *optimization; + unsigned int id; + SCOPED_AO2LOCK(lock, queue_data); + + if (queue_data->dying) { + return; + } + + if (!strcmp(local_one->uniqueid, queue_data->member_uniqueid)) { + optimization = &queue_data->member_optimize; + } else if (!strcmp(local_two->uniqueid, queue_data->caller_uniqueid)) { + optimization = &queue_data->caller_optimize; } else { - ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n"); + return; + } + + /* We only allow move-swap optimizations, so there had BETTER be a source */ + ast_assert(source != NULL); + + optimization->source_chan_uniqueid = ast_strdup(source->uniqueid); + if (!optimization->source_chan_uniqueid) { + ast_log(LOG_ERROR, "Unable to track local channel optimization for channel %s. Expect further errors\n", local_one->name); + return; } + id = ast_json_integer_get(ast_json_object_get(ast_multi_channel_blob_get_json(optimization_blob), "id")); + + optimization->id = id; + optimization->in_progress = 1; } -#endif // BUGBUG -#if 0 // BUGBUG -/*! \brief mechanism to tell if a queue caller was atxferred by a queue member. +/*! + * \internal + * \brief Handler for the end of a local channel optimization * - * When a caller is atxferred, then the queue_transfer_info datastore - * is removed from the channel. If it's still there after the bridge is - * broken, then the caller was not atxferred. + * This method takes the data gathered during the local channel optimization begin + * event and applies it to the queue stasis data appropriately. This generally involves + * updating the caller or member unique ID with the channel that is taking the place of + * the previous caller or member. * - * \note Only call this with chan locked + * \param userdata Data pertaining to the particular call in the queue. + * \param sub The stasis subscription on which the message occurred. + * \param topic The topic for this event. + * \param msg The stasis message for the local optimization end event */ -static int attended_transfer_occurred(struct ast_channel *chan) +static void handle_local_optimization_end(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) +{ + struct queue_stasis_data *queue_data = userdata; + struct ast_multi_channel_blob *optimization_blob = stasis_message_data(msg); + struct ast_channel_snapshot *local_one = ast_multi_channel_blob_get_channel(optimization_blob, "1"); + struct ast_channel_snapshot *local_two = ast_multi_channel_blob_get_channel(optimization_blob, "2"); + struct local_optimization *optimization; + int is_caller; + unsigned int id; + SCOPED_AO2LOCK(lock, queue_data); + + if (queue_data->dying) { + return; + } + + if (!strcmp(local_one->uniqueid, queue_data->member_uniqueid)) { + optimization = &queue_data->member_optimize; + is_caller = 0; + } else if (!strcmp(local_two->uniqueid, queue_data->caller_uniqueid)) { + optimization = &queue_data->caller_optimize; + is_caller = 1; + } else { + return; + } + + id = ast_json_integer_get(ast_json_object_get(ast_multi_channel_blob_get_json(optimization_blob), "id")); + + if (!optimization->in_progress) { + ast_log(LOG_WARNING, "Told of a local optimization end when we had no previous begin\n"); + return; + } + + if (id != optimization->id) { + ast_log(LOG_WARNING, "Local optimization end event ID does not match begin (%u != %u)\n", + id, optimization->id); + return; + } + + if (is_caller) { + ast_debug(3, "Local optimization: Changing queue caller uniqueid from %s to %s\n", + queue_data->caller_uniqueid, optimization->source_chan_uniqueid); + ast_string_field_set(queue_data, caller_uniqueid, optimization->source_chan_uniqueid); + } else { + ast_debug(3, "Local optimization: Changing queue member uniqueid from %s to %s\n", + queue_data->member_uniqueid, optimization->source_chan_uniqueid); + ast_string_field_set(queue_data, member_uniqueid, optimization->source_chan_uniqueid); + } + + optimization->in_progress = 0; +} + +/*! + * \internal + * \brief Handler for hangup stasis event + * + * This is how we determine that the caller or member has hung up and the call + * has ended. An appropriate queue log and stasis message are raised in this + * callback. + * + * \param userdata Data pertaining to the particular call in the queue. + * \param sub The stasis subscription on which the message occurred. + * \param topic The topic for this event. + * \param msg The stasis message for the hangup event. + */ +static void handle_hangup(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) { - return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1; + struct queue_stasis_data *queue_data = userdata; + struct ast_channel_blob *channel_blob = stasis_message_data(msg); + RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, member_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup); + enum agent_complete_reason reason; + + if (queue_data->dying) { + return; + } + + ao2_lock(queue_data); + + if (!strcmp(channel_blob->snapshot->uniqueid, queue_data->caller_uniqueid)) { + reason = CALLER; + } else if (!strcmp(channel_blob->snapshot->uniqueid, queue_data->member_uniqueid)) { + reason = AGENT; + } else { + ao2_unlock(queue_data); + return; + } + + chan = ast_channel_get_by_name(channel_blob->snapshot->name); + if (chan && ast_channel_has_role(chan, AST_TRANSFERER_ROLE_NAME)) { + /* Channel that is hanging up is doing it as part of a transfer. + * We'll get a transfer event later + */ + ao2_unlock(queue_data); + return; + } + + caller_snapshot = ast_channel_snapshot_get_latest(queue_data->caller_uniqueid); + member_snapshot = ast_channel_snapshot_get_latest(queue_data->member_uniqueid); + + ao2_unlock(queue_data); + + ast_debug(3, "Detected hangup of queue %s channel %s\n", reason == CALLER ? "caller" : "member", + channel_blob->snapshot->name); + + ast_queue_log(queue_data->queue->name, caller_snapshot->uniqueid, queue_data->member->membername, + reason == CALLER ? "COMPLETECALLER" : "COMPLETEAGENT", "%ld|%ld|%d", + (long) (queue_data->starttime - queue_data->holdstart), + (long) (time(NULL) - queue_data->starttime), queue_data->caller_pos); + + send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member, + queue_data->holdstart, queue_data->starttime, reason); + update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl, + time(NULL) - queue_data->starttime); + remove_stasis_subscriptions(queue_data); } -#endif // BUGBUG -#if 0 // BUGBUG -/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log +/*! + * \internal + * \brief Callback for all stasis channel events + * + * Based on the event and the channels involved, the work is farmed out into + * subroutines for further processing. */ -static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl) +static void queue_channel_cb(void *userdata, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *msg) { - struct ast_datastore *ds; - struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds)); + if (stasis_subscription_final_message(sub, msg)) { + ao2_cleanup(userdata); + } +} - if (!qtds) { - ast_log(LOG_WARNING, "Memory allocation error!\n"); - return NULL; +/*! + * \internal + * \brief Create stasis subscriptions for a particular call in the queue. + * + * These subscriptions are created once the call has been answered. The subscriptions + * are put in place so that call progress may be tracked. Once the call can be determined + * to have ended, then messages are logged to the queue log and stasis events are emitted. + * + * \param qe The queue entry representing the caller + * \param peer The channel that has answered the call + * \param mem The queue member that answered the call + * \param holdstart The time at which the caller entered the queue + * \param starttime The time at which the call was answered + * \param callcompletedinsl Indicates if the call was answered within the configured service level of the queue. + * \retval 0 Success + * \retval non-zero Failure + */ +static int setup_stasis_subs(struct queue_ent *qe, struct ast_channel *peer, struct member *mem, + time_t holdstart, time_t starttime, int callcompletedinsl) +{ + struct queue_stasis_data *queue_data = queue_stasis_data_alloc(qe, peer, mem, holdstart, starttime, callcompletedinsl); + + if (!queue_data) { + return -1; } - ast_channel_lock(qe->chan); - if (!(ds = ast_datastore_alloc(&queue_transfer_info, NULL))) { - ast_channel_unlock(qe->chan); - ast_free(qtds); - ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n"); - return NULL; + queue_data->bridge_router = stasis_message_router_create(ast_bridge_topic_all()); + if (!queue_data->bridge_router) { + ao2_ref(queue_data, -1); + return -1; } - qtds->qe = qe; - /* This member is refcounted in try_calling, so no need to add it here, too */ - qtds->member = member; - qtds->starttime = starttime; - qtds->callcompletedinsl = callcompletedinsl; - ds->data = qtds; - ast_channel_datastore_add(qe->chan, ds); - ast_channel_unlock(qe->chan); - return ds; + stasis_message_router_add(queue_data->bridge_router, ast_channel_entered_bridge_type(), + handle_bridge_enter, queue_data); + stasis_message_router_add(queue_data->bridge_router, ast_blind_transfer_type(), + handle_blind_transfer, queue_data); + stasis_message_router_add(queue_data->bridge_router, ast_attended_transfer_type(), + handle_attended_transfer, queue_data); + stasis_message_router_set_default(queue_data->bridge_router, + queue_bridge_cb, queue_data); + + queue_data->channel_router = stasis_message_router_create(ast_channel_topic_all()); + if (!queue_data->channel_router) { + /* Unsubscribing from the bridge router will remove the only ref of queue_data, + * thus beginning the destruction process + */ + stasis_message_router_unsubscribe(queue_data->bridge_router); + queue_data->bridge_router = NULL; + return -1; + } + + ao2_ref(queue_data, +1); + stasis_message_router_add(queue_data->channel_router, ast_local_optimization_begin_type(), + handle_local_optimization_begin, queue_data); + stasis_message_router_add(queue_data->channel_router, ast_local_optimization_end_type(), + handle_local_optimization_end, queue_data); + stasis_message_router_add(queue_data->channel_router, ast_channel_hangup_request_type(), + handle_hangup, queue_data); + stasis_message_router_set_default(queue_data->channel_router, + queue_channel_cb, queue_data); + + return 0; } -#endif // BUGBUG struct queue_end_bridge { struct call_queue *q; @@ -5312,6 +5857,82 @@ static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_ch } } +static void escape_and_substitute(struct ast_channel *chan, const char *input, + char *output, size_t size) +{ + const char *m = input; + char escaped[size]; + char *p; + + for (p = escaped; p < escaped + size - 1; p++, m++) { + switch (*m) { + case '^': + if (*(m + 1) == '{') { + *p = '$'; + } + break; + case ',': + *p++ = '\\'; + /* Fall through */ + default: + *p = *m; + } + if (*m == '\0') + break; + } + + if (p == escaped + size) { + escaped[size - 1] = '\0'; + } + + pbx_substitute_variables_helper(chan, escaped, output, size - 1); +} + +static void setup_mixmonitor(struct queue_ent *qe, const char *filename) +{ + char escaped_filename[256]; + char file_with_ext[256]; + char mixmonargs[1512]; + char escaped_monitor_exec[1024]; + const char *monitor_options; + const char *monitor_exec; + + if (filename) { + escape_and_substitute(qe->chan, filename, escaped_filename, sizeof(escaped_filename)); + } else { + ast_copy_string(escaped_filename, ast_channel_uniqueid(qe->chan), sizeof(escaped_filename)); + } + + ast_channel_lock(qe->chan); + if ((monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC"))) { + monitor_exec = ast_strdupa(monitor_exec); + } + if ((monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS"))) { + monitor_options = ast_strdupa(monitor_options); + } else { + monitor_options = ""; + } + ast_channel_unlock(qe->chan); + + if (monitor_exec) { + escape_and_substitute(qe->chan, monitor_exec, escaped_monitor_exec, sizeof(escaped_monitor_exec)); + } + + snprintf(file_with_ext, sizeof(file_with_ext), "%s.%s", escaped_filename, qe->parent->monfmt); + + if (!ast_strlen_zero(escaped_monitor_exec)) { + snprintf(mixmonargs, sizeof(mixmonargs), "b%s,%s", monitor_options, escaped_monitor_exec); + } else { + snprintf(mixmonargs, sizeof(mixmonargs), "b%s", monitor_options); + } + + ast_debug(1, "Arguments being passed to MixMonitor: %s,%s\n", file_with_ext, mixmonargs); + + if (ast_start_mixmonitor(qe->chan, file_with_ext, mixmonargs)) { + ast_log(LOG_WARNING, "Unable to start mixmonitor. Is the MixMonitor app loaded?\n"); + } +} + /*! * \internal * \brief A large function which calls members, updates statistics, and bridges the caller and a member @@ -5361,9 +5982,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a int x=0; char *announce = NULL; char digit = 0; -#if 0 // BUGBUG time_t callstart; -#endif // BUGBUG time_t now = time(NULL); struct ast_bridge_config bridge_config; char nondataquality = 1; @@ -5371,23 +5990,13 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a char *macroexec = NULL; char *gosubexec = NULL; const char *monitorfilename; - const char *monitor_exec; - const char *monitor_options; - char tmpid[256], tmpid2[256]; - char meid[1024], meid2[1024]; - char mixmonargs[1512]; - struct ast_app *mixmonapp = NULL; - char *p; + char tmpid[256]; + char meid[1024]; int forwardsallowed = 1; int block_connected_line = 0; -#if 0 // BUGBUG int callcompletedinsl; -#endif // BUGBUG struct ao2_iterator memi; struct ast_datastore *datastore; -#if 0 // BUGBUG - struct ast_datastore *transfer_ds; -#endif // BUGBUG struct queue_end_bridge *queue_end_bridge = NULL; ast_channel_lock(qe->chan); @@ -5654,9 +6263,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a time(&now); recalc_holdtime(qe, (now - qe->start)); ao2_lock(qe->parent); -#if 0 // BUGBUG callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel); -#endif // BUGBUG ao2_unlock(qe->parent); member = lpeer->member; /* Increment the refcount for this member, since we're going to be using it for awhile in here. */ @@ -5810,96 +6417,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a ast_monitor_setjoinfiles(which, 1); } } else { - mixmonapp = pbx_findapp("MixMonitor"); - - if (mixmonapp) { - ast_debug(1, "Starting MixMonitor as requested.\n"); - if (!monitorfilename) { - if (qe->chan) { - ast_copy_string(tmpid, ast_channel_uniqueid(qe->chan), sizeof(tmpid)); - } else { - snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); - } - } else { - const char *m = monitorfilename; - for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) { - switch (*m) { - case '^': - if (*(m + 1) == '{') - *p = '$'; - break; - case ',': - *p++ = '\\'; - /* Fall through */ - default: - *p = *m; - } - if (*m == '\0') - break; - } - if (p == tmpid2 + sizeof(tmpid2)) - tmpid2[sizeof(tmpid2) - 1] = '\0'; - - pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1); - } - - ast_channel_lock(qe->chan); - if ((monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC"))) { - monitor_exec = ast_strdupa(monitor_exec); - } - if ((monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS"))) { - monitor_options = ast_strdupa(monitor_options); - } else { - monitor_options = ""; - } - ast_channel_unlock(qe->chan); - - if (monitor_exec) { - const char *m = monitor_exec; - for (p = meid2; p < meid2 + sizeof(meid2) - 1; p++, m++) { - switch (*m) { - case '^': - if (*(m + 1) == '{') - *p = '$'; - break; - case ',': - *p++ = '\\'; - /* Fall through */ - default: - *p = *m; - } - if (*m == '\0') { - break; - } - } - if (p == meid2 + sizeof(meid2)) { - meid2[sizeof(meid2) - 1] = '\0'; - } - - pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1); - } - - snprintf(tmpid2, sizeof(tmpid2), "%s.%s", tmpid, qe->parent->monfmt); - - if (!ast_strlen_zero(monitor_exec)) { - snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s,%s", tmpid2, monitor_options, monitor_exec); - } else { - snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s", tmpid2, monitor_options); - } - - ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs); - /* BUGBUG - * This needs to be done differently. We need to start a MixMonitor on - * the actual queue bridge itself, not drop some channel out and pull it - * back. Once the media channel work is done, start a media channel on - * the bridge. - * - * Alternatively, don't use pbx_exec to put an audio hook on a channel. - */ - pbx_exec(qe->chan, mixmonapp, mixmonargs); - } else { - ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n"); - } + setup_mixmonitor(qe, monitorfilename); } } /* Drop out of the queue at this point, to prepare for next caller */ @@ -6008,53 +6526,10 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a queue_t_ref(qe->parent, "For bridge_config reference"); } -#if 0 // BUGBUG time(&callstart); - transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl); -#endif // BUGBUG - bridge = ast_bridge_call(qe->chan, peer, &bridge_config); - -/* BUGBUG need to do this queue logging a different way because we cannot reference peer anymore. Likely needs to be made a subscriber of stasis transfer events. */ -#if 0 // BUGBUG - /* If the queue member did an attended transfer, then the TRANSFER already was logged in the queue_log - * when the masquerade occurred. These other "ending" queue_log messages are unnecessary, except for - * the AgentComplete manager event - */ - ast_channel_lock(qe->chan); - if (!attended_transfer_occurred(qe->chan)) { - struct ast_datastore *tds; - - /* detect a blind transfer */ - if (!(ast_channel_softhangup_internal_flag(qe->chan) | ast_channel_softhangup_internal_flag(peer)) && (strcasecmp(oldcontext, ast_channel_context(qe->chan)) || strcasecmp(oldexten, ast_channel_exten(qe->chan)))) { - ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "TRANSFER", "%s|%s|%ld|%ld|%d", - ast_channel_exten(qe->chan), ast_channel_context(qe->chan), (long) (callstart - qe->start), - (long) (time(NULL) - callstart), qe->opos); - send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER); - } else if (ast_check_hangup(qe->chan) && !ast_check_hangup(peer)) { - ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "COMPLETECALLER", "%ld|%ld|%d", - (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), CALLER); - } else { - ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "COMPLETEAGENT", "%ld|%ld|%d", - (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), AGENT); - } - if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) { - ast_channel_datastore_remove(qe->chan, tds); - } - ast_channel_unlock(qe->chan); - update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart)); - } else { - ast_channel_unlock(qe->chan); - - /* We already logged the TRANSFER on the queue_log, but we still need to send the AgentComplete event */ - send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER); - } - - if (transfer_ds) { - ast_datastore_free(transfer_ds); - } -#endif // BUGBUG + setup_stasis_subs(qe, peer, member, qe->start, callstart, callcompletedinsl); + bridge = ast_bridge_call_with_flags(qe->chan, peer, &bridge_config, + AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM); res = bridge ? bridge : 1; ao2_ref(member, -1); @@ -9835,6 +10310,9 @@ static const struct ast_data_entry queue_data_providers[] = { AST_DATA_ENTRY("asterisk/application/queue/list", &queues_data_provider), }; +static struct stasis_message_router *agent_router; +static struct stasis_subscription *topic_forwarder; + static int unload_module(void) { int res; @@ -9860,6 +10338,8 @@ static int unload_module(void) stasis_message_router_remove(message_router, queue_agent_dump_type()); stasis_message_router_remove(message_router, queue_agent_ringnoanswer_type()); } + stasis_message_router_unsubscribe_and_join(agent_router); + topic_forwarder = stasis_unsubscribe(topic_forwarder); STASIS_MESSAGE_TYPE_CLEANUP(queue_caller_join_type); STASIS_MESSAGE_TYPE_CLEANUP(queue_caller_leave_type); @@ -9937,7 +10417,9 @@ static int load_module(void) int res; struct ast_flags mask = {AST_FLAGS_ALL, }; struct ast_config *member_config; - struct stasis_message_router *message_router; + struct stasis_message_router *manager_router; + struct stasis_topic *queue_topic; + struct stasis_topic *manager_topic; queues = ao2_container_alloc(MAX_QUEUE_BUCKETS, queue_hash_cb, queue_cmp_cb); @@ -10008,8 +10490,25 @@ static int load_module(void) res = -1; } - message_router = ast_manager_get_message_router(); - if (!message_router) { + manager_topic = ast_manager_get_topic(); + if (!manager_topic) { + return AST_MODULE_LOAD_DECLINE; + } + manager_router = ast_manager_get_message_router(); + if (!manager_router) { + return AST_MODULE_LOAD_DECLINE; + } + queue_topic = ast_queue_topic_all(); + if (!queue_topic) { + return AST_MODULE_LOAD_DECLINE; + } + topic_forwarder = stasis_forward_all(queue_topic, manager_topic); + if (!topic_forwarder) { + return AST_MODULE_LOAD_DECLINE; + } + + agent_router = stasis_message_router_create(ast_channel_topic_all()); + if (!agent_router) { return AST_MODULE_LOAD_DECLINE; } @@ -10030,76 +10529,86 @@ static int load_module(void) STASIS_MESSAGE_TYPE_INIT(queue_agent_dump_type); STASIS_MESSAGE_TYPE_INIT(queue_agent_ringnoanswer_type); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_caller_join_type(), queue_channel_manager_event, "QueueCallerJoin"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_caller_leave_type(), queue_channel_manager_event, "QueueCallerLeave"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_caller_abandon_type(), queue_channel_manager_event, "QueueCallerAbandon"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_member_status_type(), queue_member_manager_event, "QueueMemberStatus"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_member_added_type(), queue_member_manager_event, "QueueMemberAdded"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_member_removed_type(), queue_member_manager_event, "QueueMemberRemoved"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_member_pause_type(), queue_member_manager_event, "QueueMemberPause"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_member_penalty_type(), queue_member_manager_event, "QueueMemberPenalty"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_member_ringinuse_type(), queue_member_manager_event, "QueueMemberRinginuse"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_agent_called_type(), queue_multi_channel_manager_event, "AgentCalled"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_agent_connect_type(), queue_multi_channel_manager_event, "AgentConnect"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_agent_complete_type(), queue_multi_channel_manager_event, "AgentComplete"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_agent_dump_type(), queue_multi_channel_manager_event, "AgentDump"); - stasis_message_router_add(message_router, + stasis_message_router_add(manager_router, queue_agent_ringnoanswer_type(), queue_multi_channel_manager_event, "AgentRingNoAnswer"); + stasis_message_router_add(agent_router, + ast_channel_agent_login_type(), + queue_agent_cb, + NULL); + + stasis_message_router_add(agent_router, + ast_channel_agent_logoff_type(), + queue_agent_cb, + NULL); + ast_extension_state_add(NULL, NULL, extension_state_cb, NULL); return res ? AST_MODULE_LOAD_DECLINE : 0; diff --git a/include/asterisk/app.h b/include/asterisk/app.h index 06b903e2f..c30290f6e 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -1284,6 +1284,22 @@ struct stasis_message_type *ast_mwi_state_type(void); */ struct stasis_message_type *ast_mwi_vm_app_type(void); +/*! + * \brief Get the \ref stasis topic for queue messages + * \retval The topic structure for queue messages + * \retval NULL if it has not been allocated + * \since 12 + */ +struct stasis_topic *ast_queue_topic_all(void); + +/*! + * \brief Get the \ref stasis topic for queue messages for a particular queue name + * \param queuename The name for which to get the topic + * \retval The topic structure for queue messages for a given name + * \retval NULL if it failed to be found or allocated + * \since 12 + */ +struct stasis_topic *ast_queue_topic(const char *queuename); /*! @} */ /*! diff --git a/include/asterisk/bridge_basic.h b/include/asterisk/bridge_basic.h index 560e94d5f..483bf230e 100644 --- a/include/asterisk/bridge_basic.h +++ b/include/asterisk/bridge_basic.h @@ -33,6 +33,7 @@ extern "C" { #endif +#define AST_TRANSFERER_ROLE_NAME "transferer" /* ------------------------------------------------------------------- */ /*! @@ -126,6 +127,17 @@ extern struct ast_bridge_methods ast_bridge_basic_v_table; */ struct ast_bridge *ast_bridge_basic_new(void); +/*! + * \brief Set feature flags on a basic bridge + * + * Using this function instead of setting flags directly will + * ensure that after operations such as an attended transfer, + * the bridge will maintain the flags that were set on it. + * + * \params Flags to set on the bridge. These are added to the flags already set. + */ +void ast_bridge_basic_set_flags(struct ast_bridge *bridge, unsigned int flags); + /*! Initialize the basic bridge class for use by the system. */ void ast_bridging_init_basic(void); diff --git a/include/asterisk/core_unreal.h b/include/asterisk/core_unreal.h index 9a8d2e1f5..cd8a2cdf7 100644 --- a/include/asterisk/core_unreal.h +++ b/include/asterisk/core_unreal.h @@ -46,6 +46,11 @@ struct ast_callid; struct ast_unreal_pvt; +enum ast_unreal_channel_indicator { + AST_UNREAL_OWNER, + AST_UNREAL_CHAN, +}; + /*! * \brief Callbacks that can be provided by concrete implementations of the unreal * channel driver that will be called when events occur in the unreal layer @@ -55,8 +60,14 @@ struct ast_unreal_pvt_callbacks { * \brief Called when an optimization attempt has started * \note p is locked when this callback is called * \param p The \ref ast_unreal_pvt object + * \param source The channel that is optimizing into an unreal_pvt channel's bridge. + * If NULL, the optimization is being accomplished via a bridge merge. + * \param dest Indicator of which channel's bridge in the unreal_pvt will survive the + * optimization + * \param id Unique identifier for this optimization operation. */ - void (* const optimization_started)(struct ast_unreal_pvt *p); + void (* const optimization_started)(struct ast_unreal_pvt *p, struct ast_channel *source, + enum ast_unreal_channel_indicator dest, unsigned int id); /*! * \brief Called when an optimization attempt completed successfully @@ -64,8 +75,10 @@ struct ast_unreal_pvt_callbacks { * \param p The \ref ast_unreal_pvt object * \param success Non-zero if the optimization succeeded, zero if the optimization * met with fatal and permanent error + * \param id Unique identifier for this optimization. Same as the one from the optimization_started + * call */ - void (* const optimization_finished)(struct ast_unreal_pvt *p); + void (* const optimization_finished)(struct ast_unreal_pvt *p, int success, unsigned int id); }; /*! diff --git a/include/asterisk/features.h b/include/asterisk/features.h index 9430bc4cb..af9b77749 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -43,6 +43,20 @@ enum { int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer,struct ast_bridge_config *config); /*! + * \brief Bridge a call, and add additional flags to the bridge + * + * This does the same thing as \ref ast_bridge_call, except that once the bridge + * is created, the provided flags are set on the bridge. The provided flags are + * added to the bridge's flags; they will not clear any flags already set. + * + * \param chan The calling channel + * \param peer The called channel + * \param config Bridge configuration for the channels + * \param flags Additional flags to set on the created bridge + */ +int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, unsigned int flags); + +/*! * \brief Add an arbitrary channel to a bridge * \since 12.0.0 * diff --git a/main/app.c b/main/app.c index ee2bbf467..9410f9813 100644 --- a/main/app.c +++ b/main/app.c @@ -85,12 +85,15 @@ struct zombie { static AST_LIST_HEAD_STATIC(zombies, zombie); /* - * @{ \brief Define \ref stasis topic objects for MWI + * @{ \brief Define \ref stasis topic objects */ static struct stasis_topic *mwi_topic_all; static struct stasis_cache *mwi_state_cache; static struct stasis_caching_topic *mwi_topic_cached; static struct stasis_topic_pool *mwi_topic_pool; + +static struct stasis_topic *queue_topic_all; +static struct stasis_topic_pool *queue_topic_pool; /* @} */ /* @@ -2978,8 +2981,22 @@ struct stasis_message *ast_mwi_blob_create(struct ast_mwi_state *mwi_state, return msg; } +struct stasis_topic *ast_queue_topic_all(void) +{ + return queue_topic_all; +} + +struct stasis_topic *ast_queue_topic(const char *queuename) +{ + return stasis_topic_pool_get_topic(queue_topic_pool, queuename); +} + static void app_cleanup(void) { + ao2_cleanup(queue_topic_pool); + queue_topic_pool = NULL; + ao2_cleanup(queue_topic_all); + queue_topic_all = NULL; ao2_cleanup(mwi_topic_pool); mwi_topic_pool = NULL; ao2_cleanup(mwi_topic_all); @@ -3015,7 +3032,14 @@ int app_init(void) if (!mwi_topic_pool) { return -1; } - + queue_topic_all = stasis_topic_create("stasis_queue_topic"); + if (!queue_topic_all) { + return -1; + } + queue_topic_pool = stasis_topic_pool_create(queue_topic_all); + if (!queue_topic_pool) { + return -1; + } return 0; } diff --git a/main/bridge.c b/main/bridge.c index 30b3c0ed4..e472bfcd3 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -72,6 +72,8 @@ static struct ao2_container *bridges; static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology); +static unsigned int optimization_id; + /* Initial starting point for the bridge array of channels */ #define BRIDGE_ARRAY_START 128 @@ -2435,22 +2437,26 @@ static int try_swap_optimize_out(struct ast_bridge *chan_bridge, other = ast_bridge_channel_peer(src_bridge_channel); if (other && other->state == BRIDGE_CHANNEL_STATE_WAIT) { + unsigned int id = ast_atomic_fetchadd_int((int *) &optimization_id, +1); + ast_verb(3, "Move-swap optimizing %s <-- %s.\n", ast_channel_name(dst_bridge_channel->chan), ast_channel_name(other->chan)); if (pvt && !ast_test_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN) && pvt->callbacks && pvt->callbacks->optimization_started) { - pvt->callbacks->optimization_started(pvt); + pvt->callbacks->optimization_started(pvt, other->chan, + dst_bridge_channel->chan == pvt->owner ? AST_UNREAL_OWNER : AST_UNREAL_CHAN, + id); ast_set_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN); } other->swap = dst_bridge_channel->chan; if (!bridge_do_move(dst_bridge, other, 1, 1)) { ast_bridge_channel_leave_bridge(src_bridge_channel, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE); res = -1; - if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) { - pvt->callbacks->optimization_finished(pvt); - } + } + if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) { + pvt->callbacks->optimization_finished(pvt, res == 1, id); } } return res; @@ -2528,6 +2534,7 @@ static int try_merge_optimize_out(struct ast_bridge *chan_bridge, chan_bridge_channel, peer_bridge_channel, }; + unsigned int id; switch (bridges_allow_merge_optimization(chan_bridge, peer_bridge, ARRAY_LEN(kick_me), &merge)) { case MERGE_ALLOWED: @@ -2551,14 +2558,18 @@ static int try_merge_optimize_out(struct ast_bridge *chan_bridge, ast_channel_name(chan_bridge_channel->chan), ast_channel_name(peer_bridge_channel->chan)); + id = ast_atomic_fetchadd_int((int *) &optimization_id, +1); + if (pvt && !ast_test_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN) && pvt->callbacks && pvt->callbacks->optimization_started) { - pvt->callbacks->optimization_started(pvt); + pvt->callbacks->optimization_started(pvt, NULL, + merge.dest == ast_channel_internal_bridge(pvt->owner) ? AST_UNREAL_OWNER : AST_UNREAL_CHAN, + id); ast_set_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN); } bridge_do_merge(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me), 1); if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) { - pvt->callbacks->optimization_finished(pvt); + pvt->callbacks->optimization_finished(pvt, 1, id); } return -1; diff --git a/main/bridge_basic.c b/main/bridge_basic.c index 70556d748..283387a42 100644 --- a/main/bridge_basic.c +++ b/main/bridge_basic.c @@ -51,7 +51,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | AST_BRIDGE_FLAG_SMART) #define TRANSFER_FLAGS AST_BRIDGE_FLAG_SMART -#define TRANSFERER_ROLE_NAME "transferer" struct attended_transfer_properties; @@ -1494,7 +1493,7 @@ static void attended_transfer_properties_shutdown(struct attended_transfer_prope } if (props->transferer) { - ast_channel_remove_bridge_role(props->transferer, TRANSFERER_ROLE_NAME); + ast_channel_remove_bridge_role(props->transferer, AST_TRANSFERER_ROLE_NAME); } clear_stimulus_queue(props); @@ -2581,14 +2580,14 @@ static int bridge_personality_atxfer_push(struct ast_bridge *self, struct ast_br const char *swap_dtmf; struct bridge_basic_personality *personality = self->personality; - if (!ast_channel_has_role(bridge_channel->chan, TRANSFERER_ROLE_NAME)) { + if (!ast_channel_has_role(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME)) { return 0; } - abort_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "abort"); - complete_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "complete"); - threeway_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "threeway"); - swap_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "swap"); + abort_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "abort"); + complete_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "complete"); + threeway_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "threeway"); + swap_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "swap"); if (!ast_strlen_zero(abort_dtmf) && ast_bridge_dtmf_hook(bridge_channel->features, abort_dtmf, atxfer_abort, personality->details[personality->current].pvt, NULL, @@ -2838,11 +2837,11 @@ static int add_transferer_role(struct ast_channel *chan, struct ast_bridge_featu atxfer_swap = ast_strdupa(xfer_cfg->atxferswap); } - return ast_channel_add_bridge_role(chan, TRANSFERER_ROLE_NAME) || - ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "abort", atxfer_abort) || - ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "complete", atxfer_complete) || - ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "threeway", atxfer_threeway) || - ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "swap", atxfer_swap); + return ast_channel_add_bridge_role(chan, AST_TRANSFERER_ROLE_NAME) || + ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "abort", atxfer_abort) || + ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "complete", atxfer_complete) || + ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "threeway", atxfer_threeway) || + ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "swap", atxfer_swap); } /*! @@ -3243,6 +3242,15 @@ struct ast_bridge *ast_bridge_basic_new(void) return bridge; } +void ast_bridge_basic_set_flags(struct ast_bridge *bridge, unsigned int flags) +{ + SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock); + struct bridge_basic_personality *personality = bridge->personality; + + personality->details[personality->current].bridge_flags |= flags; + ast_set_flag(&bridge->feature_flags, flags); +} + void ast_bridging_init_basic(void) { /* Setup bridge basic subclass v_table. */ diff --git a/main/core_local.c b/main/core_local.c index 3cdff9503..8825739a6 100644 --- a/main/core_local.c +++ b/main/core_local.c @@ -96,6 +96,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <syntax> <channel_snapshot prefix="LocalOne"/> <channel_snapshot prefix="LocalTwo"/> + <channel_snapshot prefix="Source"/> + <parameter name="DestUniqueId"> + <para>The unique ID of the bridge into which the local channel is optimizing.</para> + </parameter> + <parameter name="Id"> + <para>Identification for the optimization operation.</para> + </parameter> </syntax> <see-also> <ref type="managerEvent">LocalOptimizationEnd</ref> @@ -110,6 +117,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <syntax> <channel_snapshot prefix="LocalOne"/> <channel_snapshot prefix="LocalTwo"/> + <parameter name="Success"> + <para>Indicates whether the local optimization succeeded.</para> + </parameter> + <parameter name="Id"> + <para>Identification for the optimization operation. Matches the <replaceable>Id</replaceable> + from a previous <literal>LocalOptimizationBegin</literal></para> + </parameter> </syntax> <see-also> <ref type="managerEvent">LocalOptimizationBegin</ref> @@ -127,8 +141,9 @@ static struct ast_channel *local_request(const char *type, struct ast_format_cap static int local_call(struct ast_channel *ast, const char *dest, int timeout); static int local_hangup(struct ast_channel *ast); static int local_devicestate(const char *data); -static void local_optimization_started_cb(struct ast_unreal_pvt *base); -static void local_optimization_finished_cb(struct ast_unreal_pvt *base); +static void local_optimization_started_cb(struct ast_unreal_pvt *base, struct ast_channel *source, + enum ast_unreal_channel_indicator dest, unsigned int id); +static void local_optimization_finished_cb(struct ast_unreal_pvt *base, int success, unsigned int id); static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message *msg); @@ -306,58 +321,97 @@ static int local_devicestate(const char *data) return res; } -static void publish_local_optimization(struct local_pvt *p, int complete) +static struct ast_multi_channel_blob *local_channel_optimization_blob(struct local_pvt *p, + struct ast_json *json_object) { - RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup); - RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); - RAII_VAR(struct ast_json *, blob, ast_json_null(), ast_json_unref); + struct ast_multi_channel_blob *payload; RAII_VAR(struct ast_channel_snapshot *, local_one_snapshot, NULL, ao2_cleanup); RAII_VAR(struct ast_channel_snapshot *, local_two_snapshot, NULL, ao2_cleanup); - if (!blob) { - return; - } - local_one_snapshot = ast_channel_snapshot_create(p->base.owner); if (!local_one_snapshot) { - return; + return NULL; } local_two_snapshot = ast_channel_snapshot_create(p->base.chan); if (!local_two_snapshot) { - return; + return NULL; } - payload = ast_multi_channel_blob_create(blob); + payload = ast_multi_channel_blob_create(json_object); if (!payload) { - return; + return NULL; } ast_multi_channel_blob_add_channel(payload, "1", local_one_snapshot); ast_multi_channel_blob_add_channel(payload, "2", local_two_snapshot); - msg = stasis_message_create( - complete ? ast_local_optimization_end_type() : ast_local_optimization_begin_type(), - payload); - if (!msg) { - return; - } - - stasis_publish(ast_channel_topic(p->base.owner), msg); - + return payload; } /*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_started_cb */ -static void local_optimization_started_cb(struct ast_unreal_pvt *base) +static void local_optimization_started_cb(struct ast_unreal_pvt *base, struct ast_channel *source, + enum ast_unreal_channel_indicator dest, unsigned int id) { + RAII_VAR(struct ast_json *, json_object, ast_json_null(), ast_json_unref); + RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); struct local_pvt *p = (struct local_pvt *)base; - publish_local_optimization(p, 0); + + json_object = ast_json_pack("{s: i, s: i}", + "dest", dest, "id", id); + + if (!json_object) { + return; + } + + payload = local_channel_optimization_blob(p, json_object); + if (!payload) { + return; + } + + if (source) { + RAII_VAR(struct ast_channel_snapshot *, source_snapshot, NULL, ao2_cleanup); + source_snapshot = ast_channel_snapshot_create(source); + if (!source_snapshot) { + return; + } + + ast_multi_channel_blob_add_channel(payload, "source", source_snapshot); + } + + msg = stasis_message_create(ast_local_optimization_begin_type(), payload); + if (!msg) { + return; + } + + stasis_publish(ast_channel_topic(p->base.owner), msg); } /*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_finished_cb */ -static void local_optimization_finished_cb(struct ast_unreal_pvt *base) +static void local_optimization_finished_cb(struct ast_unreal_pvt *base, int success, unsigned int id) { + RAII_VAR(struct ast_json *, json_object, ast_json_null(), ast_json_unref); + RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); struct local_pvt *p = (struct local_pvt *)base; - publish_local_optimization(p, 1); + + json_object = ast_json_pack("{s: i, s: i}", "success", success, "id", id); + + if (!json_object) { + return; + } + + payload = local_channel_optimization_blob(p, json_object); + if (!payload) { + return; + } + + msg = stasis_message_create(ast_local_optimization_end_type(), payload); + if (!msg) { + return; + } + + stasis_publish(ast_channel_topic(p->base.owner), msg); } static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message *message) @@ -384,9 +438,31 @@ static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message } if (stasis_message_type(message) == ast_local_optimization_begin_type()) { + struct ast_channel_snapshot *source_snapshot; + RAII_VAR(struct ast_str *, source_str, NULL, ast_free); + const char *dest_uniqueid; + + source_snapshot = ast_multi_channel_blob_get_channel(obj, "source"); + if (source_snapshot) { + source_str = ast_manager_build_channel_state_string_prefix(source_snapshot, "Source"); + if (!source_str) { + return NULL; + } + } + + dest_uniqueid = ast_json_object_get(blob, "dest") == AST_UNREAL_OWNER ? + local_snapshot_one->uniqueid : local_snapshot_two->uniqueid; + event = "LocalOptimizationBegin"; + if (source_str) { + ast_str_append(&event_buffer, 0, "%s", ast_str_buffer(source_str)); + } + ast_str_append(&event_buffer, 0, "DestUniqueId: %s\r\n", dest_uniqueid); + ast_str_append(&event_buffer, 0, "Id: %u\r\n", (unsigned int) ast_json_integer_get(ast_json_object_get(blob, "id"))); } else if (stasis_message_type(message) == ast_local_optimization_end_type()) { event = "LocalOptimizationEnd"; + ast_str_append(&event_buffer, 0, "Success: %s\r\n", ast_json_integer_get(ast_json_object_get(blob, "success")) ? "Yes" : "No"); + ast_str_append(&event_buffer, 0, "Id: %u\r\n", (unsigned int) ast_json_integer_get(ast_json_object_get(blob, "id"))); } else if (stasis_message_type(message) == ast_local_bridge_type()) { event = "LocalBridge"; ast_str_append(&event_buffer, 0, "Context: %s\r\n", ast_json_string_get(ast_json_object_get(blob, "context"))); diff --git a/main/features.c b/main/features.c index 2601a4106..f3e2f0fb1 100644 --- a/main/features.c +++ b/main/features.c @@ -639,19 +639,7 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, return 0; } -/*! - * \brief bridge the call and set CDR - * - * \param chan The bridge considers this channel the caller. - * \param peer The bridge considers this channel the callee. - * \param config Configuration for this bridge. - * - * Set start time, check for two channels,check if monitor on - * check for feature activation, create new CDR - * \retval res on success. - * \retval -1 on failure to bridge. - */ -int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config) +int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, unsigned int flags) { int res; struct ast_bridge *bridge; @@ -684,6 +672,8 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a return -1; } + ast_bridge_basic_set_flags(bridge, flags); + /* Put peer into the bridge */ if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) { ast_bridge_destroy(bridge); @@ -717,6 +707,23 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a return res; } +/*! + * \brief bridge the call and set CDR + * + * \param chan The bridge considers this channel the caller. + * \param peer The bridge considers this channel the callee. + * \param config Configuration for this bridge. + * + * Set start time, check for two channels,check if monitor on + * check for feature activation, create new CDR + * \retval res on success. + * \retval -1 on failure to bridge. + */ +int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config) +{ + return ast_bridge_call_with_flags(chan, peer, config, 0); +} + enum play_tone_action { PLAYTONE_NONE = 0, PLAYTONE_CHANNEL1 = (1 << 0), |