--- apps/app_confbridge.c.patched 2012-03-22 13:38:03.182595706 -0600 +++ apps/app_confbridge.c 2012-03-22 16:26:09.582599064 -0600 @@ -713,6 +713,32 @@ } /*! + * \brief Turns off MoH for the given bridge/user, unless MoH is forced on + * + * \param conference_bridge The bridge being used + * \param conference_bridge_user The involved user + * + * \return Returns 0 on success or no-op, -1 on failure + */ +static int turn_off_moh(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) +{ + if (conference_bridge_user->playing_moh) { + if (!conference_bridge_user->forced_moh) { + if (!ast_bridge_suspend(conference_bridge->bridge, conference_bridge_user->chan)) { + conference_bridge_user->playing_moh = 0; + ast_moh_stop(conference_bridge_user->chan); + ast_bridge_unsuspend(conference_bridge->bridge, conference_bridge_user->chan); + } else { + return -1; + } + } else { + conference_bridge_user->playing_moh = 0; + } + } + return 0; +} + +/*! * \brief Turns off MoH for a marked user when participants join * * \param conference_bridge Conference bridge being used @@ -728,15 +754,7 @@ /* Temporarily suspend the above participant from the bridge, if it's a marked user who can * be on hold, so we can turn off MoH if needed. */ if (ast_test_flag(&first_participant->u_profile, USER_OPT_MARKEDUSER) && ast_test_flag(&first_participant->u_profile, USER_OPT_MUSICONHOLD)) { - if (first_participant->playing_moh) { - if (!ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { - first_participant->playing_moh = 0; - ast_moh_stop(first_participant->chan); - ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan); - } else { - return -1; - } - } + return turn_off_moh(conference_bridge, first_participant); } } return 0; @@ -768,14 +786,8 @@ if (other_conference_bridge_user == conference_bridge_user) { continue; } - if ( - other_conference_bridge_user->playing_moh && - ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD) && - !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan) - ) { - other_conference_bridge_user->playing_moh = 0; - ast_moh_stop(other_conference_bridge_user->chan); - ast_bridge_unsuspend(conference_bridge->bridge, other_conference_bridge_user->chan); + if (ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) { + turn_off_moh(conference_bridge, other_conference_bridge_user); } } @@ -868,14 +880,8 @@ ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_MARKEDUSER) ) { nonwaiting_participants += 1; - if ( - other_conference_bridge_user->playing_moh && - ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD) && - !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan) - ) { - other_conference_bridge_user->playing_moh = 0; - ast_moh_stop(other_conference_bridge_user->chan); - ast_bridge_unsuspend(conference_bridge->bridge, other_conference_bridge_user->chan); + if (ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) { + turn_off_moh(conference_bridge, other_conference_bridge_user); } } } @@ -1566,14 +1566,14 @@ /* Play the Join sound to both the conference and the user entering. */ if (!quiet) { const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference_bridge_user.b_profile.sounds); - if (conference_bridge_user.playing_moh) { + if (conference_bridge_user.playing_moh || conference_bridge_user.forced_moh) { ast_moh_stop(chan); } ast_stream_and_wait(chan, join_sound, ""); ast_autoservice_start(chan); play_sound_file(conference_bridge, join_sound); ast_autoservice_stop(chan); - if (conference_bridge_user.playing_moh) { + if (conference_bridge_user.playing_moh || conference_bridge_user.forced_moh) { ast_moh_start(chan, conference_bridge_user.u_profile.moh_class, NULL); } } @@ -1965,7 +1965,7 @@ /* See if music on hold is playing */ ao2_lock(conference_bridge); - if (conference_bridge_user->playing_moh) { + if (conference_bridge_user->playing_moh || conference_bridge_user->forced_moh) { /* MOH is going, let's stop it */ ast_moh_stop(bridge_channel->chan); } @@ -1976,7 +1976,7 @@ /* See if music on hold needs to be started back up again */ ao2_lock(conference_bridge); - if (conference_bridge_user->playing_moh) { + if (conference_bridge_user->playing_moh || conference_bridge_user->forced_moh) { ast_moh_start(bridge_channel->chan, conference_bridge_user->u_profile.moh_class, NULL); } ao2_unlock(conference_bridge); @@ -2124,6 +2130,113 @@ } /* \internal + * \brief finds a conference user by channel name and turns MoH on or off. + * + * \retval 0 success + * \retval -1 conference not found + * \retval -2 user not found + */ +static int generic_moh_helper(int moh, const char *conference, const char *user) +{ + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + struct conference_bridge_user *participant = NULL; + int res = 0; + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + return -1; + } + ao2_lock(bridge); + AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { + if (!strncmp(user, ast_channel_name(participant->chan), strlen(user))) { + break; + } + } + if (participant) { + participant->forced_moh = moh; + if (!participant->playing_moh) { /* Don't start playback on channels already playing MoH, and no need to disable it on those that aren't */ + if (!ast_bridge_suspend(bridge->bridge, participant->chan)) { + if (participant->forced_moh) { + ast_moh_start(participant->chan, participant->u_profile.moh_class, NULL); + } else { + ast_moh_stop(participant->chan); + } + ast_bridge_unsuspend(bridge->bridge, participant->chan); + } + } + ast_test_suite_event_notify("CONF_HOLD", "Message: participant %s %s\r\nConference: %s\r\nChannel: %s", ast_channel_name(participant->chan), participant->forced_moh ? "moh-on" : "moh-off", bridge->b_profile.name, ast_channel_name(participant->chan)); + } else { + res = -2;; + } + ao2_unlock(bridge); + ao2_ref(bridge, -1); + + return res; +} + +static int cli_moh_helper(int moh, struct ast_cli_args *a) +{ + int res = generic_moh_helper(moh, a->argv[2], a->argv[3]); + + if (res == -1) { + ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]); + return -1; + } else if (res == -2) { + ast_cli(a->fd, "No channel named '%s' found in conference %s\n", a->argv[3], a->argv[2]); + return -1; + } + ast_cli(a->fd, "%s %s from confbridge %s\n", moh ? "moh-on" : "moh-off", a->argv[3], a->argv[2]); + return 0; +} + +static char *handle_cli_confbridge_moh_on(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "confbridge moh-on"; + e->usage = + "Usage: confbridge moh-on \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + cli_moh_helper(1, a); + + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_moh_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "confbridge moh-off"; + e->usage = + "Usage: confbridge moh-off \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + cli_moh_helper(0, a); + + return CLI_SUCCESS; +} + +/* \internal * \brief finds a conference user by channel name and mutes/unmutes them. * * \retval 0 success @@ -2297,11 +2297,11 @@ if (specifier->participant) { if (!ast_bridge_suspend(specifier->bridge->bridge, specifier->participant->chan)) { - if(specifier->participant->playing_moh){ + if(specifier->participant->playing_moh || specifier->participant->forced_moh){ ast_moh_stop(specifier->participant->chan); } ast_stream_and_wait(specifier->participant->chan, (const char *)&specifier->file, ""); - if(specifier->participant->playing_moh){ + if(specifier->participant->playing_moh || specifier->participant->forced_moh){ ast_moh_start(specifier->participant->chan, specifier->participant->u_profile.moh_class, NULL); } ast_bridge_unsuspend(specifier->bridge->bridge, specifier->participant->chan); @@ -2363,6 +2476,8 @@ AST_CLI_DEFINE(handle_cli_confbridge_kick, "Kick participants out of conference bridges."), AST_CLI_DEFINE(handle_cli_confbridge_mute, "Mute a participant."), AST_CLI_DEFINE(handle_cli_confbridge_unmute, "Unmute a participant."), + AST_CLI_DEFINE(handle_cli_confbridge_moh_on, "Force MoH playback to a participant."), + AST_CLI_DEFINE(handle_cli_confbridge_moh_off, "Stop forcing MoH playback to a participant."), AST_CLI_DEFINE(handle_cli_confbridge_lock, "Lock a conference."), AST_CLI_DEFINE(handle_cli_confbridge_unlock, "Unlock a conference."), AST_CLI_DEFINE(handle_cli_confbridge_start_record, "Start recording a conference"), @@ -2497,6 +2612,48 @@ return 0; } +static int action_moh_helper(struct mansession *s, const struct message *m, int moh) +{ + const char *conference = astman_get_header(m, "Conference"); + const char *channel = astman_get_header(m, "Channel"); + int res = 0; + + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (ast_strlen_zero(channel)) { + astman_send_error(s, m, "No channel name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + + res = generic_moh_helper(moh, conference, channel); + + if (res == -1) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } else if (res == -2) { + astman_send_error(s, m, "No Channel by that name found in Conference."); + return 0; + } + + astman_send_ack(s, m, moh ? "User MoH enforced" : "User MoH unenforced"); + return 0; +} + +static int action_confbridgemohon(struct mansession *s, const struct message *m) +{ + return action_moh_helper(s, m, 1); +} +static int action_confbridgemohoff(struct mansession *s, const struct message *m) +{ + return action_moh_helper(s, m, 0); +} + static int action_mute_unmute_helper(struct mansession *s, const struct message *m, int mute) { const char *conference = astman_get_header(m, "Conference"); @@ -2821,6 +2978,8 @@ res |= ast_manager_unregister("ConfbridgeList"); res |= ast_manager_unregister("ConfbridgeListRooms"); + res |= ast_manager_unregister("ConfbridgeMoHOn"); + res |= ast_manager_unregister("ConfbridgeMoHOff"); res |= ast_manager_unregister("ConfbridgeMute"); res |= ast_manager_unregister("ConfbridgeUnmute"); res |= ast_manager_unregister("ConfbridgeKick"); @@ -2862,6 +3021,8 @@ res |= ast_cli_register_multiple(cli_confbridge, sizeof(cli_confbridge) / sizeof(struct ast_cli_entry)); res |= ast_manager_register_xml("ConfbridgeList", EVENT_FLAG_REPORTING, action_confbridgelist); res |= ast_manager_register_xml("ConfbridgeListRooms", EVENT_FLAG_REPORTING, action_confbridgelistrooms); + res |= ast_manager_register_xml("ConfbridgeMoHOn", EVENT_FLAG_CALL, action_confbridgemohon); + res |= ast_manager_register_xml("ConfbridgeMoHOff", EVENT_FLAG_CALL, action_confbridgemohoff); res |= ast_manager_register_xml("ConfbridgeMute", EVENT_FLAG_CALL, action_confbridgemute); res |= ast_manager_register_xml("ConfbridgeUnmute", EVENT_FLAG_CALL, action_confbridgeunmute); res |= ast_manager_register_xml("ConfbridgeKick", EVENT_FLAG_CALL, action_confbridgekick); --- apps/confbridge/include/confbridge.h (revision 360189) +++ apps/confbridge/include/confbridge.h (working copy) @@ -229,6 +229,7 @@ struct ast_bridge_tech_optimizations tech_args; /*!< Bridge technology optimizations for talk detection */ unsigned int kicked:1; /*!< User has been kicked from the conference */ unsigned int playing_moh:1; /*!< MOH is currently being played to the user */ + unsigned int forced_moh:1; /*!< The user has been explicitly placed on hold and should not be subject to automatic toggles */ AST_LIST_ENTRY(conference_bridge_user) list; /*!< Linked list information */ };