@ -56,6 +56,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
# include "asterisk/manager.h"
# include "asterisk/utils.h"
# include "asterisk/adsi.h"
# include "asterisk/devicestate.h"
# include "asterisk/monitor.h"
# ifdef __AST_DEBUG_MALLOC
@ -76,6 +77,7 @@ static void FREE(void *ptr)
static char * parkedcall = " ParkedCall " ;
static int parkaddhints = 0 ; /*!< Add parking hints automatically */
static int parkingtime = DEFAULT_PARK_TIME ; /*!< No more than 45 seconds parked before you do something with them */
static char parking_con [ AST_MAX_EXTENSION ] ; /*!< Context for which parking is made accessible */
static char parking_con_dial [ AST_MAX_EXTENSION ] ; /*!< Context for dialback for parking (KLUDGE) */
@ -119,7 +121,11 @@ static char *descrip2 = "Park():"
" transfer to know the parking space). This application is always \n "
" registered internally and does not need to be explicitly added \n "
" into the dialplan, although you should include the 'parkedcalls' \n "
" context. \n " ;
" context (or the context specified in features.conf). \n \n "
" 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 " ;
static struct ast_app * monitor_app = NULL ;
static int monitor_ok = 1 ;
@ -128,6 +134,7 @@ struct parkeduser {
struct ast_channel * chan ; /*!< Parking channel */
struct timeval start ; /*!< Time the parking started */
int parkingnum ; /*!< Parking lot */
char parkingexten [ AST_MAX_EXTENSION ] ; /*!< If set beforehand, parking extension used for this call */
char context [ AST_MAX_CONTEXT ] ; /*!< Where to go if our parking time expires */
char exten [ AST_MAX_EXTENSION ] ;
int priority ;
@ -248,14 +255,14 @@ static void ast_bridge_call_thread_launch(void *data)
pthread_setschedparam ( thread , SCHED_RR , & sched ) ;
}
static int adsi_announce_park ( struct ast_channel * chan , int parkingnum )
static int adsi_announce_park ( struct ast_channel * chan , char * parkingexten )
{
int res ;
int justify [ 5 ] = { ADSI_JUST_CENT , ADSI_JUST_CENT , ADSI_JUST_CENT , ADSI_JUST_CENT } ;
char tmp [ 256 ] ;
char * message [ 5 ] = { NULL , NULL , NULL , NULL , NULL } ;
snprintf ( tmp , sizeof ( tmp ) , " Parked on % d" , parkingnum ) ;
snprintf ( tmp , sizeof ( tmp ) , " Parked on % s" , parkingexten ) ;
message [ 0 ] = tmp ;
res = adsi_load_session ( chan , NULL , 0 , 1 ) ;
if ( res = = - 1 )
@ -263,46 +270,95 @@ static int adsi_announce_park(struct ast_channel *chan, int parkingnum)
return adsi_print ( chan , message , justify , 1 ) ;
}
/*! \brief Notify metermaids that we've changed an extension */
static void notify_metermaids ( char * exten , char * context )
{
if ( option_debug > 3 )
ast_log ( LOG_DEBUG , " Notification of state change to metermaids %s@%s \n " , exten , context ) ;
/* Send notification to devicestate subsystem */
ast_device_state_changed ( " park:%s@%s " , exten , context ) ;
return ;
}
/*! \brief metermaids callback from devicestate.c */
static int metermaidstate ( const char * data )
{
int res = AST_DEVICE_INVALID ;
char * context = ast_strdupa ( data ) ;
char * exten ;
exten = strsep ( & context , " @ " ) ;
if ( ! context )
return res ;
if ( option_debug > 3 )
ast_log ( LOG_DEBUG , " Checking state of exten %s in context %s \n " , exten , context ) ;
res = ast_exists_extension ( NULL , context , exten , 1 , NULL ) ;
if ( ! res )
return AST_DEVICE_NOT_INUSE ;
else
return AST_DEVICE_INUSE ;
}
/*! \brief Park a call
\ note We put the user in the parking list , then wake up the parking thread to be sure it looks
after these channels too */
int ast_park_call ( struct ast_channel * chan , struct ast_channel * peer , int timeout , int * extout )
{
struct parkeduser * pu , * cur ;
int i , x , parking_range ;
char exten [ AST_MAX_EXTENSION ] ;
int i , x = - 1 , parking_range ;
struct ast_context * con ;
const char * parkingexten ;
/* Allocate memory for parking data */
if ( ! ( pu = ast_calloc ( 1 , sizeof ( * pu ) ) ) )
return - 1 ;
/* Lock parking lot */
ast_mutex_lock ( & parking_lock ) ;
parking_range = parking_stop - parking_start + 1 ;
for ( i = 0 ; i < parking_range ; i + + ) {
x = ( i + parking_offset ) % parking_range + parking_start ;
cur = parkinglot ;
while ( cur ) {
if ( cur - > parkingnum = = x )
/* Check for channel variable PARKINGEXTEN */
parkingexten = pbx_builtin_getvar_helper ( chan , " PARKINGEXTEN " ) ;
if ( ! ast_strlen_zero ( parkingexten ) ) {
if ( ast_exists_extension ( NULL , parking_con , parkingexten , 1 , NULL ) ) {
ast_log ( LOG_WARNING , " Requested parking extension already exists: %s@%s \n " , parkingexten , parking_con ) ;
return 0 ; /* Continue execution if possible */
}
ast_copy_string ( pu - > parkingexten , parkingexten , sizeof ( pu - > parkingexten ) ) ;
} else {
/* Select parking space within range */
parking_range = parking_stop - parking_start + 1 ;
for ( i = 0 ; i < parking_range ; i + + ) {
x = ( i + parking_offset ) % parking_range + parking_start ;
cur = parkinglot ;
while ( cur ) {
if ( cur - > parkingnum = = x )
break ;
cur = cur - > next ;
}
if ( ! cur )
break ;
cur = cur - > next ;
}
if ( ! cur )
break ;
}
if ( ! ( i < parking_range ) ) {
ast_log ( LOG_WARNING , " No more parking spaces \n " ) ;
free ( pu ) ;
ast_mutex_unlock ( & parking_lock ) ;
return - 1 ;
if ( ! ( i < parking_range ) ) {
ast_log ( LOG_WARNING , " No more parking spaces \n " ) ;
free ( pu ) ;
ast_mutex_unlock ( & parking_lock ) ;
return - 1 ;
}
/* Set pointer for next parking */
if ( parkfindnext )
parking_offset = x - parking_start + 1 ;
}
if ( parkfindnext )
parking_offset = x - parking_start + 1 ;
chan - > appl = " Parked Call " ;
chan - > data = NULL ;
pu - > chan = chan ;
/* Start music on hold */
/* Start music on hold if we have two different channels */
if ( chan ! = peer ) {
ast_indicate ( pu - > chan , AST_CONTROL_HOLD ) ; /* Indicate to peer that we're on hold */
ast_moh_start ( pu - > chan , NULL ) ;
@ -312,6 +368,7 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
pu - > parkingtime = ( timeout > 0 ) ? timeout : parkingtime ;
if ( extout )
* extout = x ;
if ( peer )
ast_copy_string ( pu - > peername , peer - > name , sizeof ( pu - > peername ) ) ;
@ -322,6 +379,7 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
pu - > priority = chan - > macropriority ? chan - > macropriority : chan - > priority ;
pu - > next = parkinglot ;
parkinglot = pu ;
/* If parking a channel directly, don't quiet yet get parking running on it */
if ( peer = = chan )
pu - > notquiteyet = 1 ;
@ -329,23 +387,25 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
/* Wake up the (presumably select()ing) thread */
pthread_kill ( parking_thread , SIGURG ) ;
if ( option_verbose > 1 )
ast_verbose ( VERBOSE_PREFIX_2 " Parked %s on %d . Will timeout back to extension [%s] %s, %d in %d seconds\n " , pu - > chan - > name , pu - > parkingnum , pu - > context , pu - > exten , pu - > priority , ( pu - > parkingtime / 1000 ) ) ;
ast_verbose ( VERBOSE_PREFIX_2 " Parked %s on %d @%s . Will timeout back to extension [%s] %s, %d in %d seconds\n " , pu - > chan - > name , pu - > parkingnum , parking_con , pu - > context , pu - > exten , pu - > priority , ( pu - > parkingtime / 1000 ) ) ;
if ( pu - > parkingnum ! = - 1 )
snprintf ( pu - > parkingexten , sizeof ( pu - > parkingexten ) , " %d " , x ) ;
manager_event ( EVENT_FLAG_CALL , " ParkedCall " ,
" Exten: %d \r \n "
" Exten: % s \r \n "
" Channel: %s \r \n "
" From: %s \r \n "
" Timeout: %ld \r \n "
" CallerID: %s \r \n "
" CallerIDName: %s \r \n " ,
pu - > parking num , pu - > chan - > name , peer ? peer - > name : " " ,
pu - > parking exte n, pu - > chan - > name , peer ? peer - > name : " " ,
( long ) pu - > start . tv_sec + ( long ) ( pu - > parkingtime / 1000 ) - ( long ) time ( NULL ) ,
S_OR ( pu - > chan - > cid . cid_num , " <unknown> " ) ,
S_OR ( pu - > chan - > cid . cid_name , " <unknown> " )
) ;
if ( peer & & adsipark & & adsi_available ( peer ) ) {
adsi_announce_park ( peer , pu - > parking num ) ;
adsi_announce_park ( peer , pu - > parking exte n) ; /* Only supports parking numbers */
adsi_unload_session ( peer ) ;
}
@ -355,11 +415,11 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
if ( ! con ) /* Still no context? Bad */
ast_log ( LOG_ERROR , " Parking context '%s' does not exist and unable to create \n " , parking_con ) ;
else { /* Add extension to context */
snprintf ( exten , sizeof ( exten ) , " %d " , x ) ;
ast_add_extension2 ( con , 1 , exten , 1 , NULL , NULL , parkedcall , strdup ( exten ) , FREE , registrar ) ;
if ( ! ast_add_extension2 ( con , 1 , pu - > parkingexten , 1 , NULL , NULL , parkedcall , strdup ( pu - > parkingexten ) , FREE , registrar ) )
notify_metermaids ( pu - > parkingexten , parking_con ) ;
}
/* Tell the peer channel the number of the parking space */
if ( peer )
if ( peer & & ! pu - > parkingnum = = - 1 ) /* Only say number if it's a number */
ast_say_digits ( peer , pu - > parkingnum , " " , peer - > language ) ;
if ( pu - > notquiteyet ) {
/* Wake up parking thread if we're really done */
@ -412,7 +472,7 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
# define FEATURE_SENSE_CHAN (1 << 0)
# define FEATURE_SENSE_PEER (1 << 1)
/*
/* ! \brief
* set caller and callee according to the direction
*/
static void set_peers ( struct ast_channel * * caller , struct ast_channel * * callee ,
@ -427,6 +487,7 @@ static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
}
}
/*! \brief support routing for one touch call parking */
static int builtin_parkcall ( struct ast_channel * chan , struct ast_channel * peer , struct ast_bridge_config * config , char * code , int sense )
{
struct ast_channel * parker ;
@ -471,7 +532,7 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
}
if ( ! monitor_app & & ! ( monitor_app = pbx_findapp ( " Monitor " ) ) ) {
monitor_ok = 0 ;
monitor_ok = 0 ;
ast_log ( LOG_ERROR , " Cannot record the call. The monitor application is disabled. \n " ) ;
return - 1 ;
}
@ -559,6 +620,7 @@ static int finishup(struct ast_channel *chan)
return res ;
}
/*! \brief Find the context for the transfer */
static const char * real_ctx ( struct ast_channel * transferer , struct ast_channel * transferee )
{
const char * s = pbx_builtin_getvar_helper ( transferer , " TRANSFER_CONTEXT " ) ;
@ -845,6 +907,7 @@ void ast_unregister_feature(struct ast_call_feature *feature)
free ( feature ) ;
}
/*! \brief Remove all features in the list */
static void ast_unregister_features ( void )
{
struct ast_call_feature * feature ;
@ -1412,14 +1475,15 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
return res ;
}
static void post_manager_event ( const char * s , int num , struct ast_channel * chan )
static void post_manager_event ( const char * s , char * parkingexten , struct ast_channel * chan )
{
manager_event ( EVENT_FLAG_CALL , s ,
" Exten: % d \r \n "
" Exten: % s \r \n "
" Channel: %s \r \n "
" CallerID: %s \r \n "
" CallerIDName: %s \r \n \r \n " ,
num , chan - > name ,
parkingexten ,
chan - > name ,
S_OR ( chan - > cid . cid_num , " <unknown> " ) ,
S_OR ( chan - > cid . cid_name , " <unknown> " )
) ;
@ -1444,7 +1508,7 @@ static void *do_parking_thread(void *ignore)
pl = NULL ;
pu = parkinglot ;
/* navigate the list with prev-cur pointers to support removals */
while ( pu ) {
while ( pu ) {
struct ast_channel * chan = pu - > chan ; /* shorthand */
int tms ; /* timeout for this item */
int x ; /* fd index in channel */
@ -1469,9 +1533,8 @@ static void *do_parking_thread(void *ignore)
con = ast_context_find ( parking_con_dial ) ;
if ( ! con ) {
con = ast_context_create ( NULL , parking_con_dial , registrar ) ;
if ( ! con ) {
if ( ! con )
ast_log ( LOG_ERROR , " Parking dial context '%s' does not exist and unable to create \n " , parking_con_dial ) ;
}
}
if ( con ) {
char returnexten [ AST_MAX_EXTENSION ] ;
@ -1485,7 +1548,7 @@ static void *do_parking_thread(void *ignore)
set_c_e_p ( chan , pu - > context , pu - > exten , pu - > priority ) ;
}
post_manager_event ( " ParkedCallTimeOut " , pu - > parking num , chan ) ;
post_manager_event ( " ParkedCallTimeOut " , pu - > parking exte n, chan ) ;
if ( option_verbose > 1 )
ast_verbose ( VERBOSE_PREFIX_2 " Timeout for %s parked on %d. Returning to %s,%s,%d \n " , chan - > name , pu - > parkingnum , chan - > context , chan - > exten , chan - > priority ) ;
@ -1503,10 +1566,10 @@ static void *do_parking_thread(void *ignore)
pu = pu - > next ;
con = ast_context_find ( parking_con ) ;
if ( con ) {
char exten [ AST_MAX_EXTENSION ] ;
snprintf ( exten , sizeof ( exten ) , " %d " , pt - > parkingnum ) ;
if ( ast_context_remove_extension2 ( con , exten , 1 , NULL ) )
if ( ast_context_remove_extension2 ( con , pt - > parkingexten , 1 , NULL ) )
ast_log ( LOG_WARNING , " Whoa, failed to remove the extension! \n " ) ;
else
notify_metermaids ( pu - > parkingexten , parking_con ) ;
} else
ast_log ( LOG_WARNING , " Whoa, no parking context? \n " ) ;
free ( pt ) ;
@ -1522,12 +1585,13 @@ static void *do_parking_thread(void *ignore)
else
ast_clear_flag ( chan , AST_FLAG_EXCEPTION ) ;
chan - > fdno = x ;
/* See if they need servicing */
f = ast_read ( chan ) ;
if ( ! f | | ( f - > frametype = = AST_FRAME_CONTROL & & f - > subclass = = AST_CONTROL_HANGUP ) ) {
if ( f )
ast_frfree ( f ) ;
post_manager_event ( " ParkedCallGiveUp " , pu - > parking num , chan ) ;
post_manager_event ( " ParkedCallGiveUp " , pu - > parking exte n, chan ) ;
/* There's a problem, hang them up*/
if ( option_verbose > 1 )
@ -1542,10 +1606,10 @@ static void *do_parking_thread(void *ignore)
pu = pu - > next ;
con = ast_context_find ( parking_con ) ;
if ( con ) {
char exten [ AST_MAX_EXTENSION ] ;
snprintf ( exten , sizeof ( exten ) , " %d " , pt - > parkingnum ) ;
if ( ast_context_remove_extension2 ( con , exten , 1 , NULL ) )
if ( ast_context_remove_extension2 ( con , pt - > parkingexten , 1 , NULL ) )
ast_log ( LOG_WARNING , " Whoa, failed to remove the extension! \n " ) ;
else
notify_metermaids ( pt - > parkingexten , parking_con ) ;
} else
ast_log ( LOG_WARNING , " Whoa, no parking context? \n " ) ;
free ( pt ) ;
@ -1593,6 +1657,7 @@ std: for (x=0; x<AST_MAX_FDS; x++) { /* mark fds for next round */
return NULL ; /* Never reached */
}
/*! \brief Park a call */
static int park_call_exec ( struct ast_channel * chan , void * data )
{
/* Data is unused at the moment but could contain a parking
@ -1604,10 +1669,13 @@ static int park_call_exec(struct ast_channel *chan, void *data)
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 = ast_park_call ( chan , chan , 0 , NULL ) ;
LOCAL_USER_REMOVE ( u ) ;
@ -1616,6 +1684,7 @@ static int park_call_exec(struct ast_channel *chan, void *data)
return res ;
}
/*! \brief Pickup parked call */
static int park_exec ( struct ast_channel * chan , void * data )
{
int res = 0 ;
@ -1627,7 +1696,7 @@ static int park_exec(struct ast_channel *chan, void *data)
struct ast_bridge_config config ;
if ( ! data ) {
ast_log ( LOG_WARNING , " Park requires an argument (extension number)\n " ) ;
ast_log ( LOG_WARNING , " Park edcall requires an argument (extension number)\n " ) ;
return - 1 ;
}
LOCAL_USER_ADD ( u ) ;
@ -1650,20 +1719,20 @@ static int park_exec(struct ast_channel *chan, void *data)
peer = pu - > chan ;
con = ast_context_find ( parking_con ) ;
if ( con ) {
char exten [ AST_MAX_EXTENSION ] ;
snprintf ( exten , sizeof ( exten ) , " %d " , pu - > parkingnum ) ;
if ( ast_context_remove_extension2 ( con , exten , 1 , NULL ) )
if ( ast_context_remove_extension2 ( con , pu - > parkingexten , 1 , NULL ) )
ast_log ( LOG_WARNING , " Whoa, failed to remove the extension! \n " ) ;
else
notify_metermaids ( pu - > parkingexten , parking_con ) ;
} else
ast_log ( LOG_WARNING , " Whoa, no parking context? \n " ) ;
manager_event ( EVENT_FLAG_CALL , " UnParkedCall " ,
" Exten: % d \r \n "
" Exten: % s \r \n "
" Channel: %s \r \n "
" From: %s \r \n "
" CallerID: %s \r \n "
" CallerIDName: %s \r \n " ,
pu - > parking num , pu - > chan - > name , chan - > name ,
pu - > parking exte n, pu - > chan - > name , chan - > name ,
S_OR ( pu - > chan - > cid . cid_num , " <unknown> " ) ,
S_OR ( pu - > chan - > cid . cid_name , " <unknown> " )
) ;
@ -1801,8 +1870,8 @@ static int handle_parkedcalls(int fd, int argc, char *argv[])
ast_mutex_lock ( & parking_lock ) ;
for ( cur = parkinglot ; cur ; cur = cur - > next ) {
ast_cli ( fd , " % 4d %25s (%-15s %-12s %-4d) %6lds\n "
, cur - > parking num , cur - > chan - > name , cur - > context , cur - > exten
ast_cli ( fd , " % -10.10s %25s (%-15s %-12s %-4d) %6lds\n "
, cur - > parking exte n, cur - > chan - > name , cur - > context , cur - > exten
, cur - > priority , cur - > start . tv_sec + ( cur - > parkingtime / 1000 ) - time ( NULL ) ) ;
numparked + + ;
@ -1829,7 +1898,7 @@ static int manager_parking_status( struct mansession *s, struct message *m )
char idText [ 256 ] = " " ;
if ( ! ast_strlen_zero ( id ) )
snprintf ( idText , 256 , " ActionID: %s \r \n " , id ) ;
snprintf ( idText , 256 , " ActionID: %s \r \n " , id ) ;
astman_send_ack ( s , m , " Parked calls will follow " ) ;
@ -1959,9 +2028,25 @@ int ast_pickup_call(struct ast_channel *chan)
return res ;
}
/*! \brief Add parking hints for all defined parking lots */
static void park_add_hints ( char * context , int start , int stop )
{
int numext ;
char device [ AST_MAX_EXTENSION ] ;
char exten [ 10 ] ;
for ( numext = start ; numext < = stop ; numext + + ) {
snprintf ( exten , sizeof ( exten ) , " %d " , numext ) ;
snprintf ( device , sizeof ( device ) , " park:%s@%s " , exten , context ) ;
ast_add_extension ( context , 1 , exten , PRIORITY_HINT , NULL , NULL , device , NULL , NULL , registrar ) ;
}
}
static int load_config ( void )
{
int start = 0 , end = 0 ;
int res ;
struct ast_context * con = NULL ;
struct ast_config * cfg = NULL ;
struct ast_variable * var = NULL ;
@ -1985,6 +2070,7 @@ static int load_config(void)
parking_stop = 750 ;
parkfindnext = 0 ;
adsipark = 0 ;
parkaddhints = 0 ;
transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT ;
featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT ;
@ -2012,6 +2098,8 @@ static int load_config(void)
}
} else if ( ! strcasecmp ( var - > name , " findslot " ) ) {
parkfindnext = ( ! strcasecmp ( var - > value , " next " ) ) ;
} else if ( ! strcasecmp ( var - > name , " parkinghints " ) ) {
parkaddhints = ast_true ( var - > value ) ;
} else if ( ! strcasecmp ( var - > name , " adsipark " ) ) {
adsipark = ast_true ( var - > value ) ;
} else if ( ! strcasecmp ( var - > name , " transferdigittimeout " ) ) {
@ -2131,7 +2219,8 @@ static int load_config(void)
/* Remove the old parking extension */
if ( ! ast_strlen_zero ( old_parking_con ) & & ( con = ast_context_find ( old_parking_con ) ) ) {
ast_context_remove_extension2 ( con , old_parking_ext , 1 , registrar ) ;
if ( ast_context_remove_extension2 ( con , old_parking_ext , 1 , registrar ) )
notify_metermaids ( old_parking_ext , old_parking_con ) ;
if ( option_debug )
ast_log ( LOG_DEBUG , " Removed old parking extension %s@%s \n " , old_parking_ext , old_parking_con ) ;
}
@ -2140,7 +2229,13 @@ static int load_config(void)
ast_log ( LOG_ERROR , " Parking context '%s' does not exist and unable to create \n " , parking_con ) ;
return - 1 ;
}
return ast_add_extension2 ( con , 1 , ast_parking_ext ( ) , 1 , NULL , NULL , parkcall , strdup ( " " ) , FREE , registrar ) ;
res = ast_add_extension2 ( con , 1 , ast_parking_ext ( ) , 1 , NULL , NULL , parkcall , strdup ( " " ) , FREE , registrar ) ;
if ( parkaddhints )
park_add_hints ( parking_con , parking_start , parking_stop ) ;
if ( ! res )
notify_metermaids ( ast_parking_ext ( ) , parking_con ) ;
return res ;
}
static int reload ( void * mod )
@ -2169,6 +2264,9 @@ static int load_module(void *mod)
ast_manager_register2 ( " Park " , EVENT_FLAG_CALL , manager_park ,
" Park a channel " , mandescr_park ) ;
}
res | = ast_devstate_prov_add ( " Park " , metermaidstate ) ;
return res ;
}
@ -2182,6 +2280,7 @@ static int unload_module(void *mod)
ast_cli_unregister ( & showfeatures ) ;
ast_cli_unregister ( & showparked ) ;
ast_unregister_application ( parkcall ) ;
ast_devstate_prov_del ( " Park " ) ;
return ast_unregister_application ( parkedcall ) ;
}