Commit 6d65f8e08c for freeswitch.com

commit 6d65f8e08cdffcd657d837a4f5cdb1b6c51d74d0
Author: yois615 <38441801+yois615@users.noreply.github.com>
Date:   Tue Mar 28 07:53:16 2023 -0400

    [mod_callcenter] Fix stale agents and UUID broadcasts

    * [call_center] Stop uuid_broadcast on answer

    * [mod_callcenter] Fix stale members in database

    When a channel is originated to an agent but the
    member fails to bridge to that agent, the database is
    not updated with the member status and a stale entry
    of 'Answered' persists until mod_callcenter is restarted.

    Additionally, cc_agent_found is set before the bridge,
    therefore ending the while loop on the member channel.
    If there is a problem with the agent bridge, the call is
    terminated prematurely.

    In this commit, we:

    * Move the SQL update of the member
    to the 'Answered' state to the agent thread instead of
    the member's thread, so that correct data is populated.

    * Reset the members state accordingly to Abandoned or
    Waiting if the channels fail to bridge.

    * Use cc_agent_bridged to end the member loop,
    so that a member is put back on queue if the agent
    channel fails to bridge.

diff --git a/src/mod/applications/mod_callcenter/mod_callcenter.c b/src/mod/applications/mod_callcenter/mod_callcenter.c
index fd7bb3dafa..b035b6d7f9 100644
--- a/src/mod/applications/mod_callcenter/mod_callcenter.c
+++ b/src/mod/applications/mod_callcenter/mod_callcenter.c
@@ -1969,9 +1969,10 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
 			playback_array(agent_session, o_announce);
 		}

-		/* This is used for the waiting caller to quit waiting for a agent */
+		/* This is used to set the reason for callcenter_function breakout */
 		switch_channel_set_variable(member_channel, "cc_agent_found", "true");
 		switch_channel_set_variable(member_channel, "cc_agent_uuid", agent_uuid);
+
 		if (switch_true(switch_channel_get_variable(member_channel, SWITCH_BYPASS_MEDIA_AFTER_BRIDGE_VARIABLE)) || switch_true(switch_channel_get_variable(agent_channel, SWITCH_BYPASS_MEDIA_AFTER_BRIDGE_VARIABLE))) {
 			switch_channel_set_flag(member_channel, CF_BYPASS_MEDIA_AFTER_BRIDGE);
 		}
@@ -1990,6 +1991,12 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
 			switch_channel_set_variable(agent_channel, "cc_agent_bridged", "false");
 			switch_channel_set_variable(member_channel, "cc_agent_bridged", "false");

+			/* Set member to Abandoned state, previous Trying */
+			sql = switch_mprintf("UPDATE members SET state = '%q', session_uuid = '', abandoned_epoch = '%" SWITCH_TIME_T_FMT "' WHERE uuid = '%q' AND instance_id = '%q'",
+				cc_member_state2str(CC_MEMBER_STATE_ABANDONED), local_epoch_time_now(NULL), h->member_uuid, globals.cc_instance_id);
+			cc_execute_sql(NULL, sql, NULL);
+			switch_safe_free(sql);
+
 			if ((o_announce = switch_channel_get_variable(member_channel, "cc_bridge_failed_outbound_announce"))) {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Playing bridge failed audio to agent %s, audio: %s\n", h->agent_name, o_announce);
 				playback_array(agent_session, o_announce);
@@ -2008,9 +2015,15 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
 			bridged = 1;
 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member \"%s\" %s is bridged to agent %s\n",
 										   h->member_cid_name, h->member_cid_number, h->agent_name);
+
 			switch_channel_set_variable(member_channel, "cc_agent_bridged", "true");
 			switch_channel_set_variable(agent_channel, "cc_agent_bridged", "true");
-			switch_channel_set_variable(member_channel, "cc_agent_uuid", agent_uuid);
+
+			/* Update member to Answered state, previous Trying */
+			sql = switch_mprintf("UPDATE members SET state = '%q', bridge_epoch = '%" SWITCH_TIME_T_FMT "' WHERE uuid = '%q' AND instance_id = '%q'",
+					cc_member_state2str(CC_MEMBER_STATE_ANSWERED), local_epoch_time_now(NULL), h->member_uuid, globals.cc_instance_id);
+			cc_execute_sql(NULL, sql, NULL);
+			switch_safe_free(sql);
 		}

 		if (bridged) {
@@ -2111,7 +2124,7 @@ static void *SWITCH_THREAD_FUNC outbound_agent_thread_run(switch_thread_t *threa
 		}

 	} else {
-		/* Agent didn't answer or originate failed */
+		/* Agent didn't answer or originate/bridge failed */
 		int delay_next_agent_call = 0;
 		switch_channel_t *member_channel = switch_core_session_get_channel(member_session);
 		switch_channel_clear_app_flag_key(CC_APP_KEY, member_channel, CC_APP_AGENT_CONNECTING);
@@ -3051,6 +3064,10 @@ SWITCH_STANDARD_APP(callcenter_function)
 	switch_channel_set_variable(member_channel, "cc_side", "member");
 	switch_channel_set_variable(member_channel, "cc_member_uuid", member_uuid);

+	/* Clear flags in case previously set */
+	switch_channel_set_variable(member_channel, "cc_agent_found", NULL);
+	switch_channel_set_variable(member_channel, "cc_agent_bridged", NULL);
+
 	/* Add manually imported score */
 	if (cc_base_score) {
 		cc_base_score_int += atoi(cc_base_score);
@@ -3178,8 +3195,8 @@ SWITCH_STANDARD_APP(callcenter_function)
 		args.buf = (void *) &ht;
 		args.buflen = sizeof(h);

-		/* An agent was found, time to exit and let the bridge do it job */
-		if ((p = switch_channel_get_variable(member_channel, "cc_agent_found")) && (agent_found = switch_true(p))) {
+		/* If the bridge didn't break the loop, break out now */
+		if ((p = switch_channel_get_variable(member_channel, "cc_agent_bridged")) && (agent_found = switch_true(p))) {
 			break;
 		}
 		/* If the member thread set a different reason, we monitor it so we can quit the wait */
@@ -3201,8 +3218,6 @@ SWITCH_STANDARD_APP(callcenter_function)
 				switch_channel_set_variable(member_channel, "cc_exit_key", buf);
 				h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_EXIT_WITH_KEY;
 				break;
-			} else if (!SWITCH_READ_ACCEPTABLE(status)) {
-				break;
 			}
 		} else {
 			switch_status_t status = switch_ivr_collect_digits_callback(member_session, &args, 0, 0);
@@ -3230,12 +3245,18 @@ SWITCH_STANDARD_APP(callcenter_function)
 		h->running = 0;
 	}

+	/* Stop uuid_broadcasts */
+	switch_core_session_flush_private_events(member_session);
+	switch_channel_stop_broadcast(member_channel);
+	switch_channel_set_flag_value(member_channel, CF_BREAK, 2);
+
 	/* Check if we were removed because FS Core(BREAK) asked us to */
 	if (h->member_cancel_reason == CC_MEMBER_CANCEL_REASON_NONE && !agent_found) {
 		h->member_cancel_reason = CC_MEMBER_CANCEL_REASON_BREAK_OUT;
 	}

 	switch_channel_set_variable(member_channel, "cc_agent_found", NULL);
+
 	/* Canceled for some reason */
 	if (!switch_channel_up(member_channel) || h->member_cancel_reason != CC_MEMBER_CANCEL_REASON_NONE) {
 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member %s <%s> abandoned waiting in queue %s\n", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")), switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), queue_name);
@@ -3282,12 +3303,6 @@ SWITCH_STANDARD_APP(callcenter_function)
 	} else {
 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Member %s <%s> is answered by an agent in queue %s\n", switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_name")), switch_str_nil(switch_channel_get_variable(member_channel, "caller_id_number")), queue_name);

-		/* Update member state */
-		sql = switch_mprintf("UPDATE members SET state = '%q', bridge_epoch = '%" SWITCH_TIME_T_FMT "' WHERE uuid = '%q' AND instance_id = '%q'",
-				cc_member_state2str(CC_MEMBER_STATE_ANSWERED), local_epoch_time_now(NULL), member_uuid, globals.cc_instance_id);
-		cc_execute_sql(NULL, sql, NULL);
-		switch_safe_free(sql);
-
 		/* Update some channel variables for xml_cdr needs */
 		switch_channel_set_variable_printf(member_channel, "cc_cause", "%s", "answered");
 		if ((queue = get_queue(queue_name))) {