In addition to making it so attended transfers don't fail unnecessarily,
authorRussell Bryant <russell@russellbryant.com>
Tue, 1 May 2007 22:24:51 +0000 (22:24 +0000)
committerRussell Bryant <russell@russellbryant.com>
Tue, 1 May 2007 22:24:51 +0000 (22:24 +0000)
add some new options to control what happens when you hangup on an attended
transfer before the target extension answers the transferred channel.  You
can now have it send the transferee back to the transferer.
(issue #8413, patch from sergee with very minor modifications by me)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@62593 65c4cc65-6c06-0410-ace0-fbb531ad65f3

configs/features.conf.sample
res/res_features.c

index a35453f..85a0d9d 100644 (file)
@@ -33,7 +33,14 @@ context => parkedcalls               ; Which context parked calls are in
 ;pickupexten = *8              ; Configure the pickup extension. (default is *8)
 ;featuredigittimeout = 500     ; Max time (ms) between digits for 
                                ; feature activation  (default is 500 ms)
-;atxfernoanswertimeout = 15    ; Timeout for answer on attended transfer default is 15 seconds.
+;atxfernoanswertimeout = 15 ; Timeout for answer on attended transfer default is 15 seconds.
+;atxferdropcall = no        ; If someone does an attended transfer, then hangs up before the transferred
+                            ; caller is connected, then by default, the system will try to call back the
+                            ; person that did the transfer.  If this is set to "yes", the callback will
+                            ; not be attempted and the transfer will just fail.
+;atxferloopdelay = 10       ; Number of seconds to sleep between retries (if atxferdropcall = no)
+;atxfercallbackretries = 2  ; Number of times to attempt to send the call back to the transferer.
+                            ; By default, this is 2.
 
 [featuremap]
 ;blindxfer => #1               ; Blind transfer  (default is #)
index 80cd787..25aa622 100644 (file)
@@ -63,6 +63,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
 #define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
 #define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000
+#define DEFAULT_ATXFER_DROP_CALL 0
+#define DEFAULT_ATXFER_LOOP_DELAY 10000
+#define DEFAULT_ATXFER_CALLBACK_RETRIES 2
 
 #define AST_MAX_WATCHERS 256
 
@@ -104,6 +107,9 @@ static int featuredigittimeout;
 static int comebacktoorigin = 1;
 
 static int atxfernoanswertimeout;
+static unsigned int atxferdropcall;
+static unsigned int atxferloopdelay;
+static unsigned int atxfercallbackretries;
 
 static char *registrar = "res_features";                  /*!< Registrar for operations */
 
@@ -219,7 +225,7 @@ static void check_goto_on_transfer(struct ast_channel *chan)
        }
 }
 
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name);
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate);
 
 
 static void *ast_bridge_call_thread(void *data) 
@@ -770,6 +776,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
        struct ast_channel *transferee;
        const char *transferer_real_context;
        char xferto[256] = "";
+       char callbackto[256] = "";
        int res;
        int outstate=0;
        struct ast_channel *newchan;
@@ -821,87 +828,181 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
 
        l = strlen(xferto);
        snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context);     /* append context */
-       newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats),
-               xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name);
-       ast_indicate(transferer, -1);
-       if (!newchan) {
-               finishup(transferee);
-               /* any reason besides user requested cancel and busy triggers the failed sound */
-               if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
+       newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+               xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+
+       if (!ast_check_hangup(transferer)) {
+               /* Transferer is up - old behaviour */
+               ast_indicate(transferer, -1);
+               if (!newchan) {
+                       finishup(transferee);
+                       /* any reason besides user requested cancel and busy triggers the failed sound */
+                       if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
                                ast_stream_and_wait(transferer, xferfailsound, ""))
+                               return -1;
+                       if (ast_stream_and_wait(transferer, xfersound, ""))
+                               ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+                       return FEATURE_RETURN_SUCCESS;
+               }
+
+               if (check_compat(transferer, newchan))
                        return -1;
-               return FEATURE_RETURN_SUCCESS;
-       }
+               memset(&bconfig,0,sizeof(struct ast_bridge_config));
+               ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
+               ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
+               res = ast_bridge_call(transferer, newchan, &bconfig);
+               if (newchan->_softhangup || !transferer->_softhangup) {
+                       ast_hangup(newchan);
+                       if (ast_stream_and_wait(transferer, xfersound, ""))
+                               ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+                       finishup(transferee);
+                       transferer->_softhangup = 0;
+                       return FEATURE_RETURN_SUCCESS;
+               }
+               if (check_compat(transferee, newchan))
+                       return -1;
+               ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+               if ((ast_autoservice_stop(transferee) < 0)
+                || (ast_waitfordigit(transferee, 100) < 0)
+                || (ast_waitfordigit(newchan, 100) < 0)
+                || ast_check_hangup(transferee)
+                || ast_check_hangup(newchan)) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+               if (!xferchan) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               /* Make formats okay */
+               xferchan->readformat = transferee->readformat;
+               xferchan->writeformat = transferee->writeformat;
+               ast_channel_masquerade(xferchan, transferee);
+               ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+               xferchan->_state = AST_STATE_UP;
+               ast_clear_flag(xferchan, AST_FLAGS_ALL);
+               xferchan->_softhangup = 0;
+               if ((f = ast_read(xferchan)))
+                       ast_frfree(f);
+               newchan->_state = AST_STATE_UP;
+               ast_clear_flag(newchan, AST_FLAGS_ALL);
+               newchan->_softhangup = 0;
+               if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+                       ast_hangup(xferchan);
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               tobj->chan = xferchan;
+               tobj->peer = newchan;
+               tobj->bconfig = *config;
 
-       if (check_compat(transferer, newchan))
-               return -1;
-       memset(&bconfig,0,sizeof(struct ast_bridge_config));
-       ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
-       ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
-       res = ast_bridge_call(transferer, newchan, &bconfig);
-       if (newchan->_softhangup || !transferer->_softhangup) {
-               ast_hangup(newchan);
-               if (ast_stream_and_wait(transferer, xfersound, ""))
+               if (ast_stream_and_wait(newchan, xfersound, ""))
                        ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
-               finishup(transferee);
-               transferer->_softhangup = 0;
-               return FEATURE_RETURN_SUCCESS;
-       }
-       
-       if (check_compat(transferee, newchan))
-               return -1;
+               ast_bridge_call_thread_launch(tobj);
+               return -1;      /* XXX meaning the channel is bridged ? */
+       } else if (!ast_check_hangup(transferee)) {
+               /* act as blind transfer */
+               if (ast_autoservice_stop(transferee) < 0) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
 
-       ast_indicate(transferee, AST_CONTROL_UNHOLD);
-       
-       if ((ast_autoservice_stop(transferee) < 0)
-          || (ast_waitfordigit(transferee, 100) < 0)
-          || (ast_waitfordigit(newchan, 100) < 0) 
-          || ast_check_hangup(transferee) 
-          || ast_check_hangup(newchan)) {
-               ast_hangup(newchan);
-               return -1;
-       }
+               if (!newchan) {
+                       unsigned int tries = 0;
 
-       xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
-       if (!xferchan) {
-               ast_hangup(newchan);
-               return -1;
-       }
-       /* Make formats okay */
-       xferchan->readformat = transferee->readformat;
-       xferchan->writeformat = transferee->writeformat;
-       ast_channel_masquerade(xferchan, transferee);
-       ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
-       xferchan->_state = AST_STATE_UP;
-       ast_clear_flag(xferchan, AST_FLAGS_ALL);        
-       xferchan->_softhangup = 0;
+                       /* newchan wasn't created - we should callback to transferer */
+                       if (!ast_exists_extension(transferer, transferer_real_context, transferer->cid.cid_num, 1, transferee->cid.cid_num)) {
+                               ast_log(LOG_WARNING, "Extension %s does not exist in context %s - callback failed\n",transferer->cid.cid_num,transferer_real_context);
+                               if (ast_stream_and_wait(transferee, "beeperr", ""))
+                                       return -1;
+                               return FEATURE_RETURN_SUCCESS;
+                       }
+                       snprintf(callbackto, sizeof(callbackto), "%s@%s/n", transferer->cid.cid_num, transferer_real_context);  /* append context */
+
+                       newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+                               callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+                       while (!newchan && !atxferdropcall && tries < atxfercallbackretries) {
+                               /* Trying to transfer again */
+                               ast_autoservice_start(transferee);
+                               ast_indicate(transferee, AST_CONTROL_HOLD);
+
+                               newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+                               xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+                               if (ast_autoservice_stop(transferee) < 0) {
+                                       ast_hangup(newchan);
+                                       return -1;
+                               }
+                               if (!newchan) {
+                                       /* Transfer failed, sleeping */
+                                       if (option_debug)
+                                               ast_log(LOG_DEBUG, "Sleeping for %d ms before callback.\n", atxferloopdelay);
+                                       ast_safe_sleep(transferee, atxferloopdelay);
+                                       if (option_debug)
+                                               ast_log(LOG_DEBUG, "Trying to callback...\n");
+                                       newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+                                               callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+                               }
+                               tries++;
+                       }
+               }
+               if (!newchan)
+                       return -1;
 
-       if ((f = ast_read(xferchan)))
-               ast_frfree(f);
+               /* newchan is up, we should prepare transferee and bridge them */
+               if (check_compat(transferee, newchan))
+                       return -1;
+               ast_indicate(transferee, AST_CONTROL_UNHOLD);
 
-       newchan->_state = AST_STATE_UP;
-       ast_clear_flag(newchan, AST_FLAGS_ALL); 
-       newchan->_softhangup = 0;
+               if ((ast_waitfordigit(transferee, 100) < 0)
+                  || (ast_waitfordigit(newchan, 100) < 0)
+                  || ast_check_hangup(transferee)
+                  || ast_check_hangup(newchan)) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
 
-       tobj = ast_calloc(1, sizeof(struct ast_bridge_thread_obj));
-       if (!tobj) {
-               ast_hangup(xferchan);
-               ast_hangup(newchan);
+               xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+               if (!xferchan) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               /* Make formats okay */
+               xferchan->readformat = transferee->readformat;
+               xferchan->writeformat = transferee->writeformat;
+               ast_channel_masquerade(xferchan, transferee);
+               ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+               xferchan->_state = AST_STATE_UP;
+               ast_clear_flag(xferchan, AST_FLAGS_ALL);
+               xferchan->_softhangup = 0;
+               if ((f = ast_read(xferchan)))
+                       ast_frfree(f);
+               newchan->_state = AST_STATE_UP;
+               ast_clear_flag(newchan, AST_FLAGS_ALL);
+               newchan->_softhangup = 0;
+               if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+                       ast_hangup(xferchan);
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               tobj->chan = xferchan;
+               tobj->peer = newchan;
+               tobj->bconfig = *config;
+
+               if (ast_stream_and_wait(newchan, xfersound, ""))
+                       ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+               ast_bridge_call_thread_launch(tobj);
+               return -1;      /* XXX meaning the channel is bridged ? */
+       } else {
+               /* Transferee hung up */
+               finishup(transferee);
                return -1;
        }
-       tobj->chan = xferchan;
-       tobj->peer = newchan;
-       tobj->bconfig = *config;
-
-       if (ast_stream_and_wait(newchan, xfersound, ""))
-               ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
-       ast_bridge_call_thread_launch(tobj);
-       return -1;      /* XXX meaning the channel is bridged ? */
 }
 
-
 /* add atxfer and automon as undefined so you can only use em if you configure them */
-#define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
+#define FEATURES_COUNT ARRAY_LEN(builtin_features)
 
 struct ast_call_feature builtin_features[] = 
  {
@@ -1152,7 +1253,7 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
 }
 
 /*! \todo XXX Check - this is very similar to the code in channel.c */
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name)
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate)
 {
        int state = 0;
        int cause = 0;
@@ -1194,7 +1295,7 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                        x = 0;
                        started = ast_tvnow();
                        to = timeout;
-                       while (!ast_check_hangup(caller) && timeout && (chan->_state != AST_STATE_UP)) {
+                       while (!((transferee && transferee->_softhangup) && (!igncallerstate && ast_check_hangup(caller))) && timeout && (chan->_state != AST_STATE_UP)) {
                                struct ast_frame *f = NULL;
 
                                monitor_chans[0] = caller;
@@ -1249,31 +1350,34 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                } else if (caller && (active_channel == caller)) {
                                        f = ast_read(caller);
                                        if (f == NULL) { /*doh! where'd he go?*/
-                                               if (caller->_softhangup && !chan->_softhangup) {
-                                                       /* make this a blind transfer */
-                                                       ready = 1;
+                                               if (!igncallerstate) {
+                                                       if (caller->_softhangup && !chan->_softhangup) {
+                                                               /* make this a blind transfer */
+                                                               ready = 1;
+                                                               break;
+                                                       }
+                                                       state = AST_CONTROL_HANGUP;
+                                                       res = 0;
                                                        break;
                                                }
-                                               state = AST_CONTROL_HANGUP;
-                                               res = 0;
-                                               break;
-                                       }
+                                       } else {
                                        
-                                       if (f->frametype == AST_FRAME_DTMF) {
-                                               dialed_code[x++] = f->subclass;
-                                               dialed_code[x] = '\0';
-                                               if (strlen(dialed_code) == len) {
-                                                       x = 0;
-                                               } else if (x && strncmp(dialed_code, disconnect_code, x)) {
-                                                       x = 0;
+                                               if (f->frametype == AST_FRAME_DTMF) {
+                                                       dialed_code[x++] = f->subclass;
                                                        dialed_code[x] = '\0';
-                                               }
-                                               if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
-                                                       /* Caller Canceled the call */
-                                                       state = AST_CONTROL_UNHOLD;
-                                                       ast_frfree(f);
-                                                       f = NULL;
-                                                       break;
+                                                       if (strlen(dialed_code) == len) {
+                                                               x = 0;
+                                                       } else if (x && strncmp(dialed_code, disconnect_code, x)) {
+                                                               x = 0;
+                                                               dialed_code[x] = '\0';
+                                                       }
+                                                       if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
+                                                               /* Caller Canceled the call */
+                                                               state = AST_CONTROL_UNHOLD;
+                                                               ast_frfree(f);
+                                                               f = NULL;
+                                                               break;
+                                                       }
                                                }
                                        }
                                }
@@ -2345,6 +2449,9 @@ static int load_config(void)
        transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
        featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
        atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+       atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+       atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
+       atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
 
        cfg = ast_config_load("features.conf");
        if (!cfg) {
@@ -2406,6 +2513,19 @@ static int load_config(void)
                                atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
                        } else
                                atxfernoanswertimeout = atxfernoanswertimeout * 1000;
+               } else if (!strcasecmp(var->name, "atxferloopdelay")) {
+                       if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value);
+                               atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+                       } else 
+                               atxferloopdelay *= 1000;
+               } else if (!strcasecmp(var->name, "atxferdropcall")) {
+                       atxferdropcall = ast_true(var->value);
+               } else if (!strcasecmp(var->name, "atxfercallbackretries")) {
+                       if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value);
+                               atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
+                       }
                } else if (!strcasecmp(var->name, "courtesytone")) {
                        ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
                }  else if (!strcasecmp(var->name, "parkedplay")) {