Merge code from team/russell/parking_updates
authorRussell Bryant <russell@russellbryant.com>
Fri, 25 Apr 2008 18:18:27 +0000 (18:18 +0000)
committerRussell Bryant <russell@russellbryant.com>
Fri, 25 Apr 2008 18:18:27 +0000 (18:18 +0000)
Add some additional features to the core park_call_full() function, and expose
them as options to the Park() application.  The functionality being added is the
ability to specify a custom return extension/context/priority, a custom timeout,
and a couple of options.  The options are to play ringing instead of MOH to the
parked caller, and to randomize parking spot selection.

(code inspired by the patch in AST-17, code from switchvox)

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

main/features.c

index 59e2d9a..a5aa645 100644 (file)
@@ -163,7 +163,8 @@ static char *parkcall = "Park";
 
 static char *synopsis2 = "Park yourself";
 
-static char *descrip2 = "Park(): "
+static char *descrip2 = 
+"   Park([timeout,[return_context,[return_exten,[return_priority,[options]]]]]):"
 "Used to park yourself (typically in combination with a supervised\n"
 "transfer to know the parking space). This application is always\n"
 "registered internally and does not need to be explicitly added\n"
@@ -172,7 +173,16 @@ static char *descrip2 = "Park(): "
 "If you set the PARKINGEXTEN variable to an extension in your\n"
 "parking context, Park() will park the call on that extension, unless\n"
 "it already exists. In that case, execution will continue at next\n"
-"priority.\n" ;
+"priority.\n"
+"   This application can accept arguments as well.\n"
+" timeout - A custom parking timeout for this parked call.\n"
+" return_context - The context to return the call to after it times out.\n"
+" return_exten - The extension to return the call to after it times out.\n"
+" return_priority - The priority to return the call to after it times out.\n"
+" options - A list of options for this parked call.  Valid options are:\n"
+"    'r' - Send ringing instead of MOH to the parked call.\n"
+"    'R' - Randomize the selection of a parking space.\n"
+"";
 
 static struct ast_app *monitor_app = NULL;
 static int monitor_ok = 1;
@@ -423,10 +433,36 @@ static enum ast_device_state metermaidstate(const char *data)
        return AST_DEVICE_INUSE;
 }
 
+/*! Options to pass to ast_park_call_full */
+enum ast_park_call_options {
+       /*! Provide ringing to the parked caller instead of music on hold */
+       AST_PARK_OPT_RINGING =   (1 << 0),
+       /*! Randomly choose a parking spot for the caller instead of choosing
+        *  the first one that is available. */
+       AST_PARK_OPT_RANDOMIZE = (1 << 1),
+};
+
+struct ast_park_call_args {
+       /*! How long to wait in the parking lot before the call gets sent back
+        *  to the specified return extension (or a best guess at where it came
+        *  from if not explicitly specified). */
+       int timeout;
+       /*! An output parameter to store the parking space where the parked caller
+        *  was placed. */
+       int *extout;
+       const char *orig_chan_name;
+       struct ast_parkinglot *parkinglot;
+       const char *return_con;
+       const char *return_ext;
+       int return_pri;
+       uint32_t flags;
+};
+
 /* Park a call */
-static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout, char *orig_chan_name, struct ast_parkinglot *parkinglot)
+static int ast_park_call_full(struct ast_channel *chan, struct ast_channel *peer, 
+       struct ast_park_call_args *args)
 {
-       struct parkeduser *pu, *cur;
+       struct parkeduser *pu;
        int i, x = -1, parking_range;
        struct ast_context *con;
        const char *parkinglotname;
@@ -437,58 +473,75 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, in
        if (parkinglotname) {
                if (option_debug)
                        ast_log(LOG_DEBUG, "Found chanvar Parkinglot: %s\n", parkinglotname);
-               parkinglot = find_parkinglot(parkinglotname);   
+               args->parkinglot = find_parkinglot(parkinglotname);     
        }
-       if (!parkinglot)
-               parkinglot = default_parkinglot;
+       if (!args->parkinglot)
+               args->parkinglot = default_parkinglot;
 
-       parkinglot_addref(parkinglot);
+       parkinglot_addref(args->parkinglot);
        if (option_debug)
-               ast_log(LOG_DEBUG, "Parkinglot: %s\n", parkinglot->name);
+               ast_log(LOG_DEBUG, "Parkinglot: %s\n", args->parkinglot->name);
 
        /* Allocate memory for parking data */
        if (!(pu = ast_calloc(1, sizeof(*pu)))) {
-               parkinglot_unref(parkinglot);
+               parkinglot_unref(args->parkinglot);
                return -1;
        }
 
        /* Lock parking list */
-       AST_LIST_LOCK(&parkinglot->parkings);
+       AST_LIST_LOCK(&args->parkinglot->parkings);
        /* Check for channel variable PARKINGEXTEN */
        parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
        if (!ast_strlen_zero(parkingexten)) {
-               if (ast_exists_extension(NULL, parkinglot->parking_con, parkingexten, 1, NULL)) {
-                       AST_LIST_UNLOCK(&parkinglot->parkings);
-                       parkinglot_unref(parkinglot);
+               if (ast_exists_extension(NULL, args->parkinglot->parking_con, parkingexten, 1, NULL)) {
+                       AST_LIST_UNLOCK(&args->parkinglot->parkings);
+                       parkinglot_unref(args->parkinglot);
                        ast_free(pu);
-                       ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parkinglot->parking_con);
+                       ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, args->parkinglot->parking_con);
                        return 1;       /* Continue execution if possible */
                }
                ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten));
                x = atoi(parkingexten);
        } else {
+               int start;
+               struct parkeduser *cur = NULL;
+
                /* Select parking space within range */
-               parking_range = parkinglot->parking_stop - parkinglot->parking_start+1;
-               for (i = 0; i < parking_range; i++) {
-                       x = (i + parkinglot->parking_offset) % parking_range + parkinglot->parking_start;
-                       AST_LIST_TRAVERSE(&parkinglot->parkings, cur, list) {
-                               if (cur->parkingnum == x)
+               parking_range = args->parkinglot->parking_stop - args->parkinglot->parking_start + 1;
+
+               if (ast_test_flag(args, AST_PARK_OPT_RANDOMIZE)) {
+                       start = ast_random() % (args->parkinglot->parking_stop - args->parkinglot->parking_start + 1);
+               } else {
+                       start = args->parkinglot->parking_start;
+               }
+
+               for (i = start; 1; i++) {
+                       if (i == args->parkinglot->parking_stop + 1) {
+                               i = args->parkinglot->parking_start - 1;
+                               continue;
+                       }
+
+                       AST_LIST_TRAVERSE(&args->parkinglot->parkings, cur, list) {
+                               if (cur->parkingnum == i) {
                                        break;
+                               }
                        }
-                       if (!cur)
+
+                       if (!cur || i == start - 1) {
                                break;
+                       }
                }
 
-               if (!(i < parking_range)) {
+               if (i == start - 1 && cur) {
                        ast_log(LOG_WARNING, "No more parking spaces\n");
                        ast_free(pu);
-                       AST_LIST_UNLOCK(&parkinglot->parkings);
-                       parkinglot_unref(parkinglot);
+                       AST_LIST_UNLOCK(&args->parkinglot->parkings);
+                       parkinglot_unref(args->parkinglot);
                        return -1;
                }
                /* Set pointer for next parking */
-               if (parkinglot->parkfindnext) 
-                       parkinglot->parking_offset = x - parkinglot->parking_start + 1;
+               if (args->parkinglot->parkfindnext) 
+                       args->parkinglot->parking_offset = x - args->parkinglot->parking_start + 1;
        }
        
        chan->appl = "Parked Call";
@@ -498,36 +551,46 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, in
        
        /* Put the parked channel on hold if we have two different channels */
        if (chan != peer) {
-               ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
-                       S_OR(parkinglot->mohclass, NULL),
-                       !ast_strlen_zero(parkinglot->mohclass) ? strlen(parkinglot->mohclass) + 1 : 0);
+               if (ast_test_flag(args, AST_PARK_OPT_RINGING)) {
+                       ast_indicate(pu->chan, AST_CONTROL_RINGING);
+               } else {
+                       ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
+                               S_OR(args->parkinglot->mohclass, NULL),
+                               !ast_strlen_zero(args->parkinglot->mohclass) ? strlen(args->parkinglot->mohclass) + 1 : 0);
+               }
        }
        
        pu->start = ast_tvnow();
        pu->parkingnum = x;
-       pu->parkinglot = parkinglot;
-       pu->parkingtime = (timeout > 0) ? timeout : parkinglot->parkingtime;
-       if (extout)
-               *extout = x;
+       pu->parkinglot = args->parkinglot;
+       pu->parkingtime = (args->timeout > 0) ? args->timeout : args->parkinglot->parkingtime;
+       if (args->extout)
+               *(args->extout) = x;
 
        if (peer) 
                ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
 
        /* Remember what had been dialed, so that if the parking
           expires, we try to come back to the same place */
-       ast_copy_string(pu->context, S_OR(chan->macrocontext, chan->context), sizeof(pu->context));
-       ast_copy_string(pu->exten, S_OR(chan->macroexten, chan->exten), sizeof(pu->exten));
-       pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
-       AST_LIST_INSERT_TAIL(&parkinglot->parkings, pu, list);
+       ast_copy_string(pu->context, 
+               S_OR(args->return_con, S_OR(chan->macrocontext, chan->context)), 
+               sizeof(pu->context));
+       ast_copy_string(pu->exten, 
+               S_OR(args->return_ext, S_OR(chan->macroexten, chan->exten)), 
+               sizeof(pu->exten));
+       pu->priority = pu->priority ? pu->priority : 
+               (chan->macropriority ? chan->macropriority : chan->priority);
+
+       AST_LIST_INSERT_TAIL(&args->parkinglot->parkings, pu, list);
 
        /* If parking a channel directly, don't quiet yet get parking running on it */
        if (peer == chan) 
                pu->notquiteyet = 1;
-       AST_LIST_UNLOCK(&parkinglot->parkings);
+       AST_LIST_UNLOCK(&args->parkinglot->parkings);
 
        /* Wake up the (presumably select()ing) thread */
        pthread_kill(parking_thread, SIGURG);
-       ast_verb(2, "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parkinglot->name, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+       ast_verb(2, "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, args->parkinglot->name, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
 
        if (pu->parkingnum != -1)
                snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
@@ -550,11 +613,11 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, in
                ast_adsi_unload_session(peer);
        }
 
-       con = ast_context_find_or_create(NULL, NULL, parkinglot->parking_con, registrar);
+       con = ast_context_find_or_create(NULL, NULL, args->parkinglot->parking_con, registrar);
        if (!con)       /* Still no context? Bad */
-               ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parkinglot->parking_con);
+               ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", args->parkinglot->parking_con);
        /* Tell the peer channel the number of the parking space */
-       if (peer && ((pu->parkingnum != -1 && ast_strlen_zero(orig_chan_name)) || !strcasecmp(peer->name, orig_chan_name))) { /* Only say number if it's a number and the channel hasn't been masqueraded away */
+       if (peer && ((pu->parkingnum != -1 && ast_strlen_zero(args->orig_chan_name)) || !strcasecmp(peer->name, args->orig_chan_name))) { /* Only say number if it's a number and the channel hasn't been masqueraded away */
                /* If a channel is masqueraded into peer while playing back the parking slot number do not continue playing it back. This is the case if an attended transfer occurs. */
                ast_set_flag(peer, AST_FLAG_MASQ_NOSTREAM);
                ast_say_digits(peer, pu->parkingnum, "", peer->language);
@@ -562,13 +625,13 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, in
        }
        if (con) {
                if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, ast_strdup(pu->parkingexten), ast_free_ptr, registrar))
-                       notify_metermaids(pu->parkingexten, parkinglot->parking_con, AST_DEVICE_INUSE);
+                       notify_metermaids(pu->parkingexten, args->parkinglot->parking_con, AST_DEVICE_INUSE);
        }
        if (pu->notquiteyet) {
                /* Wake up parking thread if we're really done */
                ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
-                       S_OR(parkinglot->mohclass, NULL),
-                       !ast_strlen_zero(parkinglot->mohclass) ? strlen(parkinglot->mohclass) + 1 : 0);
+                       S_OR(args->parkinglot->mohclass, NULL),
+                       !ast_strlen_zero(args->parkinglot->mohclass) ? strlen(args->parkinglot->mohclass) + 1 : 0);
                pu->notquiteyet = 0;
                pthread_kill(parking_thread, SIGURG);
        }
@@ -578,7 +641,12 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, in
 /*! \brief Park a call */
 int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
 {
-       return park_call_full(chan, peer, timeout, extout, NULL, NULL);
+       struct ast_park_call_args args = {
+               .timeout = timeout,
+               .extout = extout,
+       };
+
+       return ast_park_call_full(chan, peer, &args);
 }
 
 /* Park call via masquraded channel */
@@ -608,7 +676,15 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
 
        orig_chan_name = ast_strdupa(chan->name);
 
-       park_call_full(chan, peer, timeout, extout, orig_chan_name, NULL);
+       {
+               struct ast_park_call_args args = {
+                       .timeout = timeout,
+                       .extout = extout,
+                       .orig_chan_name = orig_chan_name,
+               };
+
+               ast_park_call_full(chan, peer, &args);
+       }
 
        return 0;
 }
@@ -2469,6 +2545,11 @@ struct ast_parkinglot *find_parkinglot(const char *name)
        return parkinglot;
 }
 
+AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
+       AST_APP_OPTION('r', AST_PARK_OPT_RINGING),
+       AST_APP_OPTION('R', AST_PARK_OPT_RANDOMIZE),
+END_OPTIONS );
+
 /*! \brief Park a call */
 static int park_call_exec(struct ast_channel *chan, void *data)
 {
@@ -2486,6 +2567,20 @@ static int park_call_exec(struct ast_channel *chan, void *data)
           lot context eventually */
        int res = 0;
 
+       char *parse;
+       AST_DECLARE_APP_ARGS(app_args,
+               AST_APP_ARG(timeout);
+               AST_APP_ARG(return_con);
+               AST_APP_ARG(return_ext);
+               AST_APP_ARG(return_pri);
+               AST_APP_ARG(options);
+       );
+
+       if (!ast_strlen_zero(data)) {
+               parse = ast_strdupa(data);
+               AST_STANDARD_APP_ARGS(app_args, parse);
+       }
+
        ast_copy_string(orig_exten, chan->exten, sizeof(orig_exten));
 
        /* Check if the channel has a parking lot */
@@ -2497,20 +2592,47 @@ static int park_call_exec(struct ast_channel *chan, void *data)
                parkinglot = find_parkinglot(parkinglotname);   
        }
 
-
        /* Setup the exten/priority to be s/1 since we don't know
           where this call should return */
        strcpy(chan->exten, "s");
        chan->priority = 1;
+
        /* Answer if call is not up */
        if (chan->_state != AST_STATE_UP)
                res = ast_answer(chan);
+
        /* Sleep to allow VoIP streams to settle down */
        if (!res)
                res = ast_safe_sleep(chan, 1000);
+
        /* Park the call */
        if (!res) {
-               res = park_call_full(chan, chan, 0, NULL, orig_chan_name, parkinglot);
+               struct ast_park_call_args args = {
+                       .orig_chan_name = orig_chan_name,
+                       .parkinglot = parkinglot,
+               };
+               struct ast_flags flags = { 0 };
+
+               if (!ast_strlen_zero(app_args.timeout)) {
+                       if (sscanf(app_args.timeout, "%d", &args.timeout) != 1) {
+                               ast_log(LOG_WARNING, "Invalid timeout '%s' provided\n", app_args.timeout);
+                               args.timeout = 0;
+                       }
+               }
+
+               args.return_con = app_args.return_con;
+               args.return_ext = app_args.return_ext;
+               if (!ast_strlen_zero(app_args.return_pri)) {
+                       if (sscanf(app_args.return_pri, "%d", &args.return_pri) != 1) {
+                               ast_log(LOG_WARNING, "Invalid priority '%s' specified\n", app_args.return_pri);
+                               args.return_pri = 0;
+                       }
+               }
+
+               ast_app_parse_options(park_call_options, &flags, NULL, NULL);
+               args.flags = flags.flags;
+
+               res = ast_park_call_full(chan, chan, &args);
                /* Continue on in the dialplan */
                if (res == 1) {
                        ast_copy_string(chan->exten, orig_exten, sizeof(chan->exten));