}
}
+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);
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
*
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)
{
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,