/* * Asterisk -- A telephony toolkit for Linux. * * Mark Spencer * * Valet Parking derived from original Asterisk Parking * Copyright (C) 2004, Anthony Minessale * Anthony Minessale * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_VALETPARK_TIME 45000 static struct ast_channel *valet_request(const char *type, int format, void *data, int *cause); static const struct ast_channel_tech valet_tech = { .type = "Valet", .description = "Valet Unpark", .requester = valet_request, .capabilities = AST_FORMAT_SLINEAR, }; 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(||[|][|][|])\n" "Auto-Sense Valet Parking: if is not occupied, park it, if it is already parked, bridge to it.\n\n"; static char *vpcdesc = "ValetParkCall(||[|][|][|])\n" "Park Call at in until someone calls ValetUnparkCall on the same + \n" "set to 'auto' to auto-choose the slot.\n\n"; static char *vupdesc = "ValetUnparkCall(|)\n" "Un-Park the call at in lot use 'fifo' or 'filo' for auto-ordered Un-Park.\n\n"; static char *vldesc = "ValetParkList()\n" "Audibly list the slot number of all the calls in 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]; char channame[AST_MAX_EXTENSION]; int priority; int valetparkingtime; int new; int old; 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;xlanguage); 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); if(chan->name) strncpy(pu->channame,chan->name,sizeof(pu->channame)-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, ast_strlen_zero(chan->musicclass) ? "default" : chan->musicclass); 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); pbx_builtin_setvar_helper(pu->chan,"Parker","Yes"); 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" "CallerIDName: %s\r\n\r\n" ,pu->valetparkingnum, pu->chan->name, lotname ,(long)pu->start.tv_sec + (long)(pu->valetparkingtime/1000) - (long)time(NULL) ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "") ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "") ); 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 */ if((f = ast_read(chan))) 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 res; 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) { if( !pbx_builtin_getvar_helper(pu->chan,"BLINDTRANSFER") && pu->chan && (!strcmp(pu->chan->name,pu->channame))) { if(!pu->new) { ast_moh_stop(pu->chan); ast_safe_sleep(pu->chan, 1000); pu->new++; gc = 10; } if(! (res = ast_streamfile(pu->chan, "vm-extension", pu->chan->language))) { if (! (res = ast_waitstream(pu->chan, ""))) { if ( !(res = ast_say_digits(pu->chan,pu->valetparkingnum, "", pu->chan->language))) { ast_safe_sleep(pu->chan, 5000); } } } } else if(! pu->old) { gc = 0; ast_indicate(pu->chan, -1); pu->old++; } 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, ast_strlen_zero(pu->chan->musicclass) ? "default" : pu->chan->musicclass); } 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;xchan->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)) ast_set_flag(pu->chan, AST_FLAG_EXCEPTION); 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;xchan->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); if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->cid.cid_num)) { ast_explicit_goto(chan, chan->context, chan->exten, chan->priority + 100); return 0; } 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 struct ast_channel *do_valetunpark(struct ast_channel *chan, char *exten, char *lotname) { int res=0; struct ast_channel *peer=NULL; struct valetparkeduser *pu, *pl=NULL; int valetpark=-1; struct ast_channel *rchan = NULL; char tmp[80]; 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(chan && !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 NULL; 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); return NULL; } } 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) { rchan = pu->chan; peer = pu->chan; free(pu); } return rchan; } static struct ast_channel *valet_request(const char *type, int format, void *data, int *cause) { char *exten = NULL, *lotname = NULL; struct ast_channel *peer; if(!data || !(exten = ast_strdupa(data))) { ast_log(LOG_WARNING,"No Memory!\n"); return NULL; } if((lotname=strchr(exten,':'))) { *lotname = '\0'; *lotname++; } if(!lotname) { ast_log(LOG_WARNING,"Please specify a lotname in the dialplan."); *cause = AST_CAUSE_UNALLOCATED; return NULL; } if((peer = do_valetunpark(NULL, exten, lotname))) { if(ast_test_flag(peer, AST_FLAG_MOH)) { ast_moh_stop(peer); } if(ast_set_read_format(peer, format) || ast_set_write_format(peer, format)) { ast_log(LOG_WARNING,"Hanging up on %s because I cant make it the requested format.\n",peer->name); ast_hangup(peer); *cause = AST_CAUSE_UNALLOCATED; return NULL; } /* We return the chan we have been protecting which is already up but be vewy vewy qwiet we will trick asterisk into thinking it's a new channel */ ast_setstate(peer, AST_STATE_RESERVED); } return peer; } static int valetunpark_call(struct ast_channel *chan, void *data) { int res=0; struct localuser *u; struct ast_channel *peer=NULL; int valetpark=-1; int dres; struct ast_bridge_config config; char *exten,*lotname; if (!data) { ast_log(LOG_WARNING, "ValetUnpark requires an argument (extension number)\n"); return -1; } exten=lotname=NULL; if(!data || !(exten = ast_strdupa(data))) { ast_log(LOG_WARNING,"No Memory!\n"); return -1; } 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); /* JK02: it helps to answer the channel if not already up */ if (chan->_state != AST_STATE_UP) { ast_answer(chan); } peer = do_valetunpark(chan, exten, lotname); 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)); ast_set_flag(&(config.features_caller) , AST_FEATURE_REDIRECT); ast_set_flag(&(config.features_callee) , AST_FEATURE_REDIRECT); res = ast_bridge_call(chan,peer,&config); 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" "CallerIDName: %s\r\n" "\r\n" ,cur->valetparkingnum, cur->chan->name ,(long)cur->start.tv_sec + (long)(cur->valetparkingtime/1000) - (long)time(NULL) ,(cur->chan->cid.cid_num ? cur->chan->cid.cid_num : "") ,(cur->chan->cid.cid_name ? cur->chan->cid.cid_name : "") ); 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); ast_channel_register(&valet_tech); 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_channel_unregister(&valet_tech); 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; }