diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index 6d141bd4e1f6b947748665b9b3cd63e7d440a8a2..7fe727e53034ac0dcd616865140872af690a878b 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -587,6 +587,43 @@ static void send_conf_stasis(struct confbridge_conference *conference, struct as } } +static void send_conf_stasis_snapshots(struct confbridge_conference *conference, + struct ast_channel_snapshot *chan_snapshot, struct stasis_message_type *type, + struct ast_json *extras) +{ + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref); + RAII_VAR(struct ast_bridge_snapshot *, bridge_snapshot, NULL, ao2_cleanup); + + json_object = ast_json_pack("{s: s}", + "conference", conference->name); + if (!json_object) { + return; + } + + if (extras) { + ast_json_object_update(json_object, extras); + } + + ast_bridge_lock(conference->bridge); + bridge_snapshot = ast_bridge_snapshot_create(conference->bridge); + ast_bridge_unlock(conference->bridge); + if (!bridge_snapshot) { + return; + } + + msg = ast_bridge_blob_create_from_snapshots(type, + bridge_snapshot, + chan_snapshot, + json_object); + if (!msg) { + return; + } + + stasis_publish(ast_bridge_topic(conference->bridge), msg); +} + + static void send_conf_start_event(struct confbridge_conference *conference) { send_conf_stasis(conference, NULL, confbridge_start_type(), NULL, 0); @@ -1479,6 +1516,126 @@ static int push_announcer(struct confbridge_conference *conference) return 0; } +static void confbridge_unlock_and_unref(void *obj) +{ + struct confbridge_conference *conference = obj; + + if (!obj) { + return; + } + ao2_unlock(conference); + ao2_ref(conference, -1); +} + +void confbridge_handle_atxfer(struct ast_attended_transfer_message *msg) +{ + struct ast_channel_snapshot *old_snapshot; + struct ast_channel_snapshot *new_snapshot; + char *confbr_name = NULL; + char *comma; + RAII_VAR(struct confbridge_conference *, conference, NULL, confbridge_unlock_and_unref); + struct confbridge_user *user = NULL; + int found_user = 0; + struct ast_json *json_object; + + if (msg->to_transferee.channel_snapshot + && strcmp(msg->to_transferee.channel_snapshot->dialplan->appl, "ConfBridge") == 0 + && msg->target) { + /* We're transferring a bridge to an extension */ + old_snapshot = msg->to_transferee.channel_snapshot; + new_snapshot = msg->target; + } else if (msg->to_transfer_target.channel_snapshot + && strcmp(msg->to_transfer_target.channel_snapshot->dialplan->appl, "ConfBridge") == 0 + && msg->transferee) { + /* We're transferring a call to a bridge */ + old_snapshot = msg->to_transfer_target.channel_snapshot; + new_snapshot = msg->transferee; + } else { + ast_log(LOG_ERROR, "Could not determine proper channels\n"); + return; + } + + /* + * old_snapshot->data should have the original parameters passed to + * the ConfBridge app: + * conference[,bridge_profile[,user_profile[,menu]]] + * We'll use "conference" to look up the bridge. + * + * We _could_ use old_snapshot->bridgeid to get the bridge but + * that would involve locking the conference_bridges container + * and iterating over it looking for a matching bridge. + */ + if (ast_strlen_zero(old_snapshot->dialplan->data)) { + ast_log(LOG_ERROR, "Channel '%s' didn't have app data set\n", old_snapshot->base->name); + return; + } + confbr_name = ast_strdupa(old_snapshot->dialplan->data); + comma = strchr(confbr_name, ','); + if (comma) { + *comma = '\0'; + } + + ast_debug(1, "Confbr: %s Leaving: %s Joining: %s\n", confbr_name, old_snapshot->base->name, new_snapshot->base->name); + + conference = ao2_find(conference_bridges, confbr_name, OBJ_SEARCH_KEY); + if (!conference) { + ast_log(LOG_ERROR, "Conference bridge '%s' not found\n", confbr_name); + return; + } + ao2_lock(conference); + + /* + * We need to grab the user profile for the departing user in order to + * properly format the join/leave messages. + */ + AST_LIST_TRAVERSE(&conference->active_list, user, list) { + if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) { + found_user = 1; + break; + } + } + + /* + * If we didn't find the user in the active list, try the waiting list. + */ + if (!found_user && conference->waitingusers) { + AST_LIST_TRAVERSE(&conference->waiting_list, user, list) { + if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) { + found_user = 1; + break; + } + } + } + + if (!found_user) { + ast_log(LOG_ERROR, "Unable to find user profile for channel '%s' in bridge '%s'\n", + old_snapshot->base->name, confbr_name); + return; + } + + /* + * We're going to use the existing user profile to create the messages. + */ + json_object = ast_json_pack("{s: b}", + "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN) + ); + if (!json_object) { + return; + } + + send_conf_stasis_snapshots(conference, old_snapshot, confbridge_leave_type(), json_object); + ast_json_unref(json_object); + + json_object = ast_json_pack("{s: b, s: b}", + "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN), + "muted", user->muted); + if (!json_object) { + return; + } + send_conf_stasis_snapshots(conference, new_snapshot, confbridge_join_type(), json_object); + ast_json_unref(json_object); +} + /*! * \brief Join a conference bridge * diff --git a/apps/confbridge/confbridge_manager.c b/apps/confbridge/confbridge_manager.c index b1819caf615a6b7c4a0363a62383020dda534449..06cb43321aef3614299d3973eaf14083d5b6ae28 100644 --- a/apps/confbridge/confbridge_manager.c +++ b/apps/confbridge/confbridge_manager.c @@ -621,6 +621,26 @@ static void confbridge_join_cb(void *data, struct stasis_subscription *sub, ast_free(extra_text); } +static void confbridge_atxfer_cb(void *data, struct stasis_subscription *sub, + struct stasis_message *message) +{ + struct ast_attended_transfer_message *msg = stasis_message_data(message); + + if (msg->result != AST_BRIDGE_TRANSFER_SUCCESS) { + return; + } + + /* + * This callback will get called for ALL attended transfers + * so we need to make sure this transfer belongs to + * a conference bridge before trying to handle it. + */ + if (msg->dest_type == AST_ATTENDED_TRANSFER_DEST_APP + && strcmp(msg->dest.app, "ConfBridge") == 0) { + confbridge_handle_atxfer(msg); + } +} + static void confbridge_start_record_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { @@ -739,6 +759,13 @@ int manager_confbridge_init(void) manager_confbridge_shutdown(); return -1; } + if (stasis_message_router_add(bridge_state_router, + ast_attended_transfer_type(), + confbridge_atxfer_cb, + NULL)) { + manager_confbridge_shutdown(); + return -1; + } if (stasis_message_router_add(bridge_state_router, confbridge_leave_type(), confbridge_leave_cb, diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h index 237431e7d9db59650f65b06f3490865724a2e9f3..f932da8587a951e5fad1ad8948ee89819616ed3d 100644 --- a/apps/confbridge/include/confbridge.h +++ b/apps/confbridge/include/confbridge.h @@ -28,6 +28,7 @@ #include "asterisk/channel.h" #include "asterisk/bridge.h" #include "asterisk/bridge_features.h" +#include "asterisk/stasis_bridges.h" #include "conf_state.h" /*! Maximum length of a conference bridge name */ @@ -714,4 +715,14 @@ struct confbridge_conference *conf_find_bridge(const char *conference_name); void conf_send_event_to_participants(struct confbridge_conference *conference, struct ast_channel *chan, struct stasis_message *msg); +/*! + * \brief Create join/leave events for attended transfers + * \since 13.28 + * \since 16.5 + * + * \param msg The attended transfer stasis message + * + */ +void confbridge_handle_atxfer(struct ast_attended_transfer_message *msg); + #endif diff --git a/include/asterisk/stasis_bridges.h b/include/asterisk/stasis_bridges.h index 4d80955c42a74b13b092f3c96cfad62d22ea283d..3d6d2b2e354b2e088614c30fec4b22cd25ac48e3 100644 --- a/include/asterisk/stasis_bridges.h +++ b/include/asterisk/stasis_bridges.h @@ -166,6 +166,29 @@ struct stasis_message *ast_bridge_blob_create(struct stasis_message_type *type, struct ast_channel *chan, struct ast_json *blob); +/*! + * \since 13.28 + * \since 16.5 + * \brief Creates a \ref ast_bridge_blob message from snapshots. + * + * The \a blob JSON object requires a \c "type" field describing the blob. It + * should also be treated as immutable and not modified after it is put into the + * message. + * + * \pre bridge is locked. + * \pre No channels are locked. + * + * \param bridge_snapshot Bridge snapshot + * \param channel_snapshot Channel snapshot + * \param blob JSON object representing the data. + * \return \ref ast_bridge_blob message. + * \return \c NULL on error + */ +struct stasis_message *ast_bridge_blob_create_from_snapshots(struct stasis_message_type *type, + struct ast_bridge_snapshot *bridge_snapshot, + struct ast_channel_snapshot *chan_snapshot, + struct ast_json *blob); + /*! * \since 12 * \brief Publish a bridge channel enter event diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c index 31d3eac3980af88c510ba506afa2b348e7471bd7..9238adc65435c974a09fc64cd5e95c2c69f56a1a 100644 --- a/main/stasis_bridges.c +++ b/main/stasis_bridges.c @@ -543,6 +543,42 @@ struct stasis_message *ast_bridge_blob_create( return msg; } +struct stasis_message *ast_bridge_blob_create_from_snapshots( + struct stasis_message_type *message_type, + struct ast_bridge_snapshot *bridge_snapshot, + struct ast_channel_snapshot *chan_snapshot, + struct ast_json *blob) +{ + struct ast_bridge_blob *obj; + struct stasis_message *msg; + + if (!message_type) { + return NULL; + } + + obj = ao2_alloc(sizeof(*obj), bridge_blob_dtor); + if (!obj) { + return NULL; + } + + if (bridge_snapshot) { + obj->bridge = ao2_bump(bridge_snapshot); + } + + if (chan_snapshot) { + obj->channel = ao2_bump(chan_snapshot); + } + + if (blob) { + obj->blob = ast_json_ref(blob); + } + + msg = stasis_message_create(message_type, obj); + ao2_ref(obj, -1); + + return msg; +} + void ast_bridge_publish_enter(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap) {