Nenad Radosavljevic
2005-Jun-20 10:15 UTC
[Asterisk-Users] Re: app_valetparking.c for * STABLE (1.0.X)
Nope ! This is the one that tries to include PRE 1.0.X header file <parking.h>. It cannot compile on * 1.0.X (I have tried also to include <features.h> instead of <parking.h> (as far as I know features.h is successor to parking.h), but still without results). Thanks anyway. Nenad> > Try this > >> Since www.bkw.org seems not to exist anymore (getting response from >> some hosting provider), does anyone happend to have a copy of >> app_valetparking.c from www.bkw.org - the one that should work with * >> stable 1.0.X ? If so please contact me. >> >> One that can be downloaded from www.loligo.com dosn't compile with >> 1.0.X, and SuperValletParking (www.asterlink.com/svp/) seems to be for >> * HEAD (1.1.X), so it wont do me any good. >>
Paul Zimm
2005-Jun-20 11:15 UTC
[Asterisk-Users] Re: app_valetparking.c for * STABLE (1.0.X)
Oops, I sent the wrong one. Here's one I modified to work with 1.0.X Try again> Nope ! This is the one that tries to include PRE 1.0.X header file > <parking.h>. > > It cannot compile on * 1.0.X (I have tried also to include > <features.h> instead of <parking.h> (as far as I know features.h is > successor to parking.h), but still without results). > > Thanks anyway. > > Nenad > >> >> Try this >-------------- next part -------------- /* * Asterisk -- A telephony toolkit for Linux. * * Routines implementing call valetparking * * Copyright (C) 1999, Mark Spencer * * Mark Spencer <markster@linux-support.net> * * This program is free software, distributed under the terms of * the GNU General Public License */ #include <asterisk/lock.h> #include <asterisk/file.h> #include <asterisk/logger.h> #include <asterisk/channel.h> #include <asterisk/pbx.h> #include <asterisk/options.h> #include <asterisk/module.h> #include <asterisk/translate.h> #include <asterisk/say.h> #include <asterisk/callerid.h> #include <asterisk/channel_pvt.h> #include <asterisk/features.h> #include <asterisk/musiconhold.h> #include <asterisk/config.h> #include <asterisk/cli.h> #include <asterisk/utils.h> #include <asterisk/app.h> #include <asterisk/manager.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <sys/time.h> #include <sys/signal.h> #include <netinet/in.h> #include <pthread.h> #define DEFAULT_VALETPARK_TIME 45000 static char *valetparking = "ValetParking"; static char *valetparkedcall = "ValetParkCall"; static char *valetunparkedcall = "ValetUnparkCall"; static char *valetparklist = "ValetParkList"; /* No more than 45 seconds valetparked before you do something with them */ static int valetparkingtime = DEFAULT_VALETPARK_TIME; /* First available extension for valetparking */ static int valetparking_start = 1; /* Last available extension for valetparking */ static int valetparking_stop = 10000; static char *vpsynopsis = "Valet Parking"; static char *vpcsynopsis = "Valet Park Call"; static char *vupsynopsis = "Valet UnPark Call"; static char *vlsynopsis = "ValetParkList"; static char *vpdesc "ValetParking(<exten>|<lotname>|<timeout>[|<return_ext>][|<return_pri>][|<return_context>])\n" "Auto-Sense Valet Parking: if <exten> is not occupied, park it, if it is already parked, bridge to it.\n\n"; static char *vpcdesc "ValetParkCall(<exten>|<lotname>|<timeout>[|<return_ext>][|<return_pri>][|<return_context>])\n" "Park Call at <exten> in <lotname> until someone calls ValetUnparkCall on the same <exten> + <lotname>\n" "set <exten> to 'auto' to auto-choose the slot.\n\n"; static char *vupdesc "ValetUnparkCall(<exten>|<lotname>)\n" "Un-Park the call at <exten> in lot <lotname> use 'fifo' or 'filo' for auto-ordered Un-Park.\n\n"; static char *vldesc "ValetParkList(<lotname>)\n" "Audibly list the slot number of all the calls in <lotname> press * to unpark it.\n\n"; struct valetparkeduser { struct ast_channel *chan; struct timeval start; int valetparkingnum; /* Where to go if our valetparking time expires */ char context[AST_MAX_EXTENSION]; char exten[AST_MAX_EXTENSION]; char lotname[AST_MAX_EXTENSION]; int priority; int valetparkingtime; struct valetparkeduser *next; }; static struct valetparkeduser *valetparkinglot; AST_MUTEX_DEFINE_STATIC(valetparking_lock); static pthread_t valetparking_thread; STANDARD_LOCAL_USER; LOCAL_USER_DECL; static int valetparking_count(void) { struct valetparkeduser *cur; int x=0; ast_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) x++; ast_mutex_unlock(&valetparking_lock); return x; } static int valetparking_say(struct ast_channel *chan,char *lotname) { struct valetparkeduser *cur; int x=0,y=0,res=0; int list[1024]; if(!lotname) return 0; ast_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) if(cur->lotname && !strcmp(lotname,cur->lotname)) list[y++] = cur->valetparkingnum; ast_mutex_unlock(&valetparking_lock); for(x=0;x<y;x++) { ast_say_digits(chan,list[x], "", chan->language); res = ast_waitfordigit(chan,1500); if(res != 0) { res = list[x]; break; } } return res; } static int ast_pop_valetparking_top(char *lotname) { struct valetparkeduser *cur; ast_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) if(cur->lotname && !strcmp(lotname,cur->lotname)) break; ast_mutex_unlock(&valetparking_lock); return cur ? cur->valetparkingnum : 0; } static int ast_pop_valetparking_bot(char *lotname) { struct valetparkeduser *cur,*last=NULL; ast_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) { if(cur->lotname && !strcmp(lotname,cur->lotname)) { last = cur; } } ast_mutex_unlock(&valetparking_lock); return last ? last->valetparkingnum : 0; } static int ast_is_valetparked(char *exten,char *lotname) { struct valetparkeduser *cur; int ext=0; int ret = 0; ext = atoi(exten); if(! ext > 0) { return ret; } ast_mutex_lock(&valetparking_lock); cur = valetparkinglot; while(cur) { if (cur->valetparkingnum == ext && lotname && cur->lotname && !strcmp(lotname,cur->lotname)) { ret = 1; break; } cur = cur->next; } ast_mutex_unlock(&valetparking_lock); return ret; } static int ast_valetpark_call(struct ast_channel *chan, int timeout, int *extout,char *lotname) { /* We put the user in the valetparking list, then wake up the valetparking thread to be sure it looks after these channels too */ struct valetparkeduser *pu, *cur; int x; x = *extout; pu = malloc(sizeof(struct valetparkeduser)); if (pu) { memset(pu,0,sizeof(struct valetparkeduser)); ast_mutex_lock(&valetparking_lock); if(lotname) { strncpy(pu->lotname,lotname,sizeof(pu->lotname)); if(chan->exten) strncpy(pu->exten,chan->exten,sizeof(pu->exten)-1); if(chan->context) strncpy(pu->context,chan->context,sizeof(pu->context)-1); pu->priority = chan->priority; x = *extout; if(x == -1) { for (x=valetparking_start;x<=valetparking_stop;x++) { for(cur = valetparkinglot;cur;cur=cur->next) { if (cur->valetparkingnum == x && cur->lotname && !strcmp(cur->lotname,lotname)) break; } if (!cur) break; } } } if (x <= valetparking_stop) { chan->appl = "Valet Parked Call"; chan->data = NULL; pu->chan = chan; /* Start music on hold */ ast_moh_start(pu->chan, NULL); gettimeofday(&pu->start, NULL); pu->valetparkingnum = x; if (timeout >= 0) pu->valetparkingtime = timeout; else pu->valetparkingtime = valetparkingtime; *extout = x; /* Remember what had been dialed, so that if the valetparking expires, we try to come back to the same place */ if (strlen(chan->macrocontext)) strncpy(pu->context, chan->macrocontext, sizeof(pu->context)-1); else strncpy(pu->context, chan->context, sizeof(pu->context)-1); if (strlen(chan->macroexten)) strncpy(pu->exten, chan->macroexten, sizeof(pu->exten)-1); else strncpy(pu->exten, chan->exten, sizeof(pu->exten)-1); if (chan->macropriority) pu->priority = chan->macropriority; else pu->priority = chan->priority; pu->next = valetparkinglot; valetparkinglot = pu; ast_mutex_unlock(&valetparking_lock); /* Wake up the (presumably select()ing) thread */ pthread_kill(valetparking_thread, SIGURG); if (option_verbose > 1) ast_verbose(VERBOSE_PREFIX_2 "Valet Parked %s on slot %d\n", pu->chan->name, pu->valetparkingnum); manager_event(EVENT_FLAG_CALL, "VirtualValetparkedCall", "Exten: %d\r\n" "Channel: %s\r\n" "LotName: %s\r\n" "Timeout: %ld\r\n" "CallerID: %s\r\n" ,pu->valetparkingnum, pu->chan->name, lotname ,(long)pu->start.tv_sec + (long)(pu->valetparkingtime/1000) - (long)time(NULL) ,(pu->chan->callerid ? pu->chan->callerid : "") ); return 0; } else { ast_log(LOG_WARNING, "No more valetparking spaces\n"); free(pu); ast_mutex_unlock(&valetparking_lock); return -1; } } else { ast_log(LOG_WARNING, "Out of memory\n"); return -1; } return 0; } static int ast_masq_valetpark_call(struct ast_channel *rchan,int timeout, int *extout,char *lotname) { struct ast_channel *chan; struct ast_frame *f; /* Make a new, fake channel that we'll use to masquerade in the real one */ chan = ast_channel_alloc(0); if (chan) { /* Let us keep track of the channel name */ snprintf(chan->name, sizeof (chan->name), "ValetParked/%s",rchan->name); /* Make formats okay */ chan->readformat = rchan->readformat; chan->writeformat = rchan->writeformat; ast_channel_masquerade(chan, rchan); /* Setup the extensions and such */ strncpy(chan->context, rchan->context, sizeof(chan->context) - 1); strncpy(chan->exten, rchan->exten, sizeof(chan->exten) - 1); chan->priority = rchan->priority; /* Make the masq execute */ f = ast_read(chan); if (f) ast_frfree(f); ast_valetpark_call(chan, timeout, extout,lotname); } else { ast_log(LOG_WARNING, "Unable to create Valet Parked channel\n"); return -1; } return 0; } static void *do_valetparking_thread(void *ignore) { int ms, tms, max; struct valetparkeduser *pu, *pl, *pt = NULL; struct timeval tv; struct ast_frame *f; int x; int gc=0; fd_set rfds, efds; fd_set nrfds, nefds; FD_ZERO(&rfds); FD_ZERO(&efds); for (;;) { ms = -1; max = -1; ast_mutex_lock(&valetparking_lock); pl = NULL; pu = valetparkinglot; gettimeofday(&tv, NULL); FD_ZERO(&nrfds); FD_ZERO(&nefds); while(pu) { tms = (tv.tv_sec - pu->start.tv_sec) * 1000 + (tv.tv_usec - pu->start.tv_usec) / 1000; if(gc < 5 && !pu->chan->generator) { gc++; ast_moh_start(pu->chan,NULL); } if(pu->valetparkingtime > 0 && tms > pu->valetparkingtime) { /* They've been waiting too long, send them back to where they came. Theoretically they should have their original extensions and such, but we copy to be on the safe side */ strncpy(pu->chan->exten, pu->exten, sizeof(pu->chan->exten)-1); strncpy(pu->chan->context, pu->context, sizeof(pu->chan->context)-1); pu->chan->priority = pu->priority; /* Stop music on hold */ ast_moh_stop(pu->chan); /* Start up the PBX, or hang them up */ if (ast_pbx_start(pu->chan)) { ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", pu->chan->name); ast_hangup(pu->chan); } /* And take them out of the valetparking lot */ if (pl) pl->next = pu->next; else valetparkinglot = pu->next; pt = pu; pu = pu->next; free(pt); } else { for (x=0;x<AST_MAX_FDS;x++) { if ((pu->chan->fds[x] > -1) && (FD_ISSET(pu->chan->fds[x], &rfds) || FD_ISSET(pu->chan->fds[x], &efds))) { if (FD_ISSET(pu->chan->fds[x], &efds)) pu->chan->exception = 1; pu->chan->fdno = x; /* See if they need servicing */ f = ast_read(pu->chan); if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) { /* There's a problem, hang them up*/ if (option_verbose > 1) ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being Valet Parked\n", pu->chan->name); ast_hangup(pu->chan); /* And take them out of the valetparking lot */ if (pl) pl->next = pu->next; else valetparkinglot = pu->next; pt = pu; pu = pu->next; free(pt); break; } else { /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */ ast_frfree(f); goto std; /* XXX Ick: jumping into an else statement??? XXX */ } } } if (x >= AST_MAX_FDS) { std: for (x=0;x<AST_MAX_FDS;x++) { /* Keep this one for next one */ if (pu->chan->fds[x] > -1) { FD_SET(pu->chan->fds[x], &nrfds); FD_SET(pu->chan->fds[x], &nefds); if (pu->chan->fds[x] > max) max = pu->chan->fds[x]; } } /* Keep track of our longest wait */ if ((tms < ms) || (ms < 0)) ms = tms; pl = pu; pu = pu->next; } } } ast_mutex_unlock(&valetparking_lock); rfds = nrfds; efds = nefds; tv.tv_sec = ms / 1000; tv.tv_usec = (ms % 1000) * 1000; /* Wait for something to happen */ ast_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL); pthread_testcancel(); } return NULL; /* Never reached */ } static int ast_valetparking(struct ast_channel *chan, void *data) { struct localuser *u; char *appname; char buf[512],*exten,*lotname,*to; struct ast_app *app; int res=0; if (!data) { ast_log(LOG_WARNING, "ValetParking requires an argument (extension number)\n"); return -1; } exten=lotname=to=NULL; strncpy(buf,data,512); exten = buf; if((lotname=strchr(exten,'|'))) { *lotname = '\0'; *lotname++; if((to=strchr(lotname,'|'))) { *to = '\0'; *to++; } } if(exten[0] >= 97) { ast_log(LOG_WARNING, "ValetParking requires a numeric extension.\n"); return -1; } appname = ast_is_valetparked(exten,lotname) ? "ValetParkCall" : "ValetUnparkCall"; app = pbx_findapp(appname); LOCAL_USER_ADD(u); if(app) { res = pbx_exec(chan,app,data,1); } else { ast_log(LOG_WARNING, "Error: Can't find app %s\n",appname); res = -1; } LOCAL_USER_REMOVE(u); return res; } static int valetpark_call(struct ast_channel *chan, void *data) { struct localuser *u; int timeout = DEFAULT_VALETPARK_TIME; int ext = 0,res = 0; char buf[512],*exten,*lotname,*to,*findme,*context,*priority=NULL,tmp[80]; if (!data) { ast_log(LOG_WARNING, "ValetParkCall requires an argument (extension number)\n"); return -1; } exten=lotname=to=findme=context=NULL; strncpy(buf,data,512); exten = buf; if((lotname=strchr(exten,'|'))) { *lotname = '\0'; *lotname++; if((to=strchr(lotname,'|'))) { *to = '\0'; *to++; timeout = atoi(to) * 1000; if((findme=strchr(to,'|'))) { *findme = '\0'; *findme++; if((priority=strchr(findme,'|'))) { *priority = '\0'; *priority++; if((context=strchr(priority,'|'))) { *context = '\0'; *context++; } } } } } if(!lotname) { ast_log(LOG_WARNING,"Please specify a lotname in the dialplan."); return -1; } if(ast_is_valetparked(exten,lotname)) { ast_log(LOG_WARNING,"Call is already Valet Parked Here [%s]\n",exten); return -1; } LOCAL_USER_ADD(u); ast_answer(chan); if(exten && lotname) { if(!strcmp(exten,"auto")) ext = -1; else if(!strcmp(exten,"query")) { ast_waitfor(chan,-1); memset(&tmp,0,80); ast_streamfile(chan, "vm-extension", chan->language); res = ast_waitstream(chan, AST_DIGIT_ANY); if(res) return -1; ast_app_getdata(chan,"vm-then-pound",tmp,80,5000); if(tmp[0]) ext = atoi(tmp); } else { ext = atoi(exten); } if(ext == 0) ext = -1; if(findme) strncpy(chan->exten,findme,sizeof(chan->exten)-1); if (context) strncpy(chan->context, context, sizeof(chan->context)-1); if(priority) { chan->priority = atoi(priority); if(!chan->priority) chan->priority = 1; } ast_masq_valetpark_call(chan,timeout,&ext,lotname); } LOCAL_USER_REMOVE(u); return 1; } static int valetpark_list(struct ast_channel *chan, void *data) { struct localuser *u; int res=0; struct ast_app *app; char buf[512]; if(!data) { ast_log(LOG_WARNING,"Parameter 'lotname' is required.\n"); return -1; } LOCAL_USER_ADD(u); res = valetparking_say(chan,data); if(res > 0) { app = pbx_findapp("ValetUnparkCall"); if(app) { snprintf(buf,512,"%d|%s",res,(char *)data); res = pbx_exec(chan,app,buf,1); } } LOCAL_USER_REMOVE(u); return 1; } static int valetunpark_call(struct ast_channel *chan, void *data) { int res=0; struct localuser *u; struct ast_channel *peer=NULL; struct valetparkeduser *pu, *pl=NULL; int valetpark=-1; int dres; struct ast_bridge_config config; char buf[512],tmp[80],*exten,*lotname; if (!data) { ast_log(LOG_WARNING, "Valetpark requires an argument (extension number)\n"); return -1; } exten=lotname=NULL; strncpy(buf,data,512); exten = buf; if((lotname=strchr(exten,'|'))) { *lotname = '\0'; *lotname++; } if(!lotname) { ast_log(LOG_WARNING,"Please specify a lotname in the dialplan."); return -1; } LOCAL_USER_ADD(u); ast_answer(chan); if(exten) { if(!strcmp(exten,"fifo")) { valetpark = ast_pop_valetparking_top(lotname); } else if(!strcmp(exten,"filo")) { valetpark = ast_pop_valetparking_bot(lotname); } else if(!strcmp(exten,"query")) { ast_waitfor(chan,-1); memset(&tmp,0,80); ast_streamfile(chan, "vm-extension", chan->language); res = ast_waitstream(chan, AST_DIGIT_ANY); if(res) return -1; ast_app_getdata(chan,"vm-then-pound",tmp,80,5000); if(tmp[0]) valetpark = atoi(tmp); } else { valetpark = atoi(exten); } if(valetpark == 0) { ast_log(LOG_WARNING, "Nobody Valet Parked in %s",lotname); LOCAL_USER_REMOVE(u); return -1; } } else valetpark = atoi((char *)data); ast_mutex_lock(&valetparking_lock); pu = valetparkinglot; while(pu) { if ((lotname && pu->valetparkingnum == valetpark && pu->lotname && !strcmp(pu->lotname,lotname)) || (! lotname && pu->valetparkingnum == valetpark)) { if (pl) pl->next = pu->next; else valetparkinglot = pu->next; break; } pl = pu; pu = pu->next; } ast_mutex_unlock(&valetparking_lock); if (pu) { peer = pu->chan; free(pu); } /* JK02: it helps to answer the channel if not already up */ if (chan->_state != AST_STATE_UP) { ast_answer(chan); } if (peer) { ast_moh_stop(peer); res = ast_channel_make_compatible(chan, peer); if (res < 0) { ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name); ast_hangup(peer); LOCAL_USER_REMOVE(u); return -1; } /* This runs sorta backwards, since we give the incoming channel control, as if it were the person called. */ if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to Valet Parked call %d in lot %s\n", chan->name, valetpark,lotname); memset(&config,0,sizeof(struct ast_bridge_config)); config.allowredirect_in = 1; config.allowredirect_out = 1; config.allowdisconnect_in = 0; config.allowdisconnect_out = 0; config.timelimit = 0; config.play_warning = 0; config.warning_freq = 0; config.warning_sound=NULL; res = ast_bridge_call(chan,peer,&config); /* Simulate the PBX hanging up */ if (res != AST_PBX_NO_HANGUP_PEER) ast_hangup(peer); LOCAL_USER_REMOVE(u); return res; } else { /* XXX Play a message XXX */ dres = ast_streamfile(chan, "pbx-invalidpark", chan->language); if (!dres) { dres = ast_waitstream(chan, ""); } else { ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name); res = 0; } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Channel %s tried to talk to non-existant Valet Parked call %d\n", chan->name, valetpark); res = -1; } LOCAL_USER_REMOVE(u); return res; } static int handle_valetparkedcalls(int fd, int argc, char *argv[]) { struct valetparkeduser *cur; ast_cli(fd, "%4s %25s (%-15s %-12s %-4s) %-6s %-6s %-15s\n", "Num", "Channel" , "Context", "Extension", "Pri", "Elapsed","Timeout","LotName"); ast_mutex_lock(&valetparking_lock); cur=valetparkinglot; while(cur) { ast_cli(fd, "%4d %25s (%-15s %-12s %-4d) %6lds %6lds %-15s\n" ,cur->valetparkingnum, cur->chan->name, cur->context, cur->exten ,cur->priority,(time(NULL) - cur->start.tv_sec),cur->valetparkingtime ? (cur->start.tv_sec + (cur->valetparkingtime/1000) - time(NULL)) : 0,cur->lotname); cur = cur->next; } ast_mutex_unlock(&valetparking_lock); return RESULT_SUCCESS; } static char showvaletparked_help[] "Usage: show valetparkedcalls\n" " Lists currently Valet Parked calls.\n"; static struct ast_cli_entry showvaletparked { { "show", "valetparkedcalls", NULL }, handle_valetparkedcalls, "Lists valetparked calls", showvaletparked_help }; /* Dump lot status */ static int manager_valetparking_status( struct mansession *s, struct message *m ) { struct valetparkeduser *cur; astman_send_ack(s, m, "Valet Parked calls will follow"); ast_mutex_lock(&valetparking_lock); cur=valetparkinglot; while(cur) { ast_cli(s->fd, "Event: ValetParkedCall\r\n" "Exten: %d\r\n" "Channel: %s\r\n" "Timeout: %ld\r\n" "CallerID: %s\r\n" "\r\n" ,cur->valetparkingnum, cur->chan->name ,(long)cur->start.tv_sec + (long)(cur->valetparkingtime/1000) - (long)time(NULL) ,(cur->chan->callerid ? cur->chan->callerid : "") ); cur = cur->next; } ast_mutex_unlock(&valetparking_lock); return RESULT_SUCCESS; } int load_module(void) { int res; ast_cli_register(&showvaletparked); valetparkingtime = DEFAULT_VALETPARK_TIME; ast_pthread_create(&valetparking_thread, NULL, do_valetparking_thread, NULL); res = ast_register_application(valetunparkedcall, valetunpark_call, vupsynopsis, vupdesc); res = ast_register_application(valetparkedcall, valetpark_call, vpcsynopsis, vpcdesc); res = ast_register_application(valetparking, ast_valetparking, vpsynopsis, vpdesc); res = ast_register_application(valetparklist,valetpark_list, vlsynopsis, vldesc); if (!res) { ast_manager_register( "ValetparkedCalls", 0, manager_valetparking_status, "List valetparked calls" ); } return res; } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; if (!ast_mutex_lock(&valetparking_lock)) { if (valetparking_thread && (valetparking_thread != AST_PTHREADT_STOP)) { pthread_cancel(valetparking_thread); pthread_kill(valetparking_thread, SIGURG); pthread_join(valetparking_thread, NULL); } valetparking_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&valetparking_lock); } else { ast_log(LOG_WARNING, "Unable to lock the valet\n"); return -1; } ast_manager_unregister( "ValetparkedCalls" ); ast_cli_unregister(&showvaletparked); ast_unregister_application(valetunparkedcall); ast_unregister_application(valetparkedcall); ast_unregister_application(valetparking); ast_unregister_application(valetparklist); return 0; } char *description(void) { return "Valet Parking Application"; } int usecount(void) { int res; STANDARD_USECOUNT(res); res += valetparking_count(); return res; } char *key() { return ASTERISK_GPL_KEY; }