/* * CallWeaver -- An open source telephony toolkit. * * This program is free software, distributed under the terms of * the GNU General Public License * * Mark Spencer * * Valet Parking derived from original asterisk Parking * Copyright (C) 2004, Anthony Minessale * Anthony Minessale * * Come To ClueCon Aug-3-5 (http://www.cluecon.com) * * 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 "callweaver.h" CALLWEAVER_FILE_VERSION("$HeadURL$", "$Revision$") #include "callweaver/lock.h" #include "callweaver/file.h" #include "callweaver/logger.h" #include "callweaver/channel.h" #include "callweaver/pbx.h" #include "callweaver/options.h" #include "callweaver/causes.h" #include "callweaver/module.h" #include "callweaver/translate.h" #include "callweaver/utils.h" #include "callweaver/say.h" #include "callweaver/phone_no_utils.h" #include "callweaver/features.h" #include "callweaver/musiconhold.h" #include "callweaver/config.h" #include "callweaver/cli.h" #include "callweaver/app.h" #include "callweaver/manager.h" #include "callweaver/devicestate.h" #include "callweaver/keywords.h" #define DEFAULT_VALETPARK_TIME 45000 static struct cw_channel *valet_request(const char *type, int format, void *data, int *cause); static int valetparked_devicestate(void *data); static const struct cw_channel_tech valet_tech = { .type = "Valet", .description = "Valet Unpark", .requester = valet_request, .capabilities = CW_FORMAT_SLINEAR, .devicestate = valetparked_devicestate, }; static void *valetparking_app; static void *valetparkedcall_app; static void *valetunparkedcall_app; static void *valetparklist_app; static const char valetparking[] = "ValetParking"; static const char valetparkedcall[] = "ValetParkCall"; static const char valetunparkedcall[] = "ValetUnparkCall"; static const 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 const char vpsynopsis[] = "Valet Parking"; static const char vpcsynopsis[] = "Valet Park Call"; static const char vupsynopsis[] = "Valet UnPark Call"; static const char vlsynopsis[] = "ValetParkList"; static const char vpsyntax[] = "ValetParking(exten, lotname, timeout[, return_ext][, return_pri][, return_context])"; static const char vpcsyntax[] = "ValetParkCall(exten, lotname, timeout[, return_ext][, return_pri][, return_context])"; static const char vupsyntax[] = "ValetUnparkCall(exten, lotname)"; static const char vlsyntax[] = "ValetParkList(lotname)"; static const char vpdesc[] = "Auto-Sense Valet Parking: if is not occupied, park it, if it is already parked, bridge to it.\n"; static const char vpcdesc[] = "Park Call at in until someone calls ValetUnparkCall on the same + \n" "set to 'auto' to auto-choose the slot.\n"; static const char vupdesc[] = "Un-Park the call at in lot use 'fifo' or 'filo' for auto-ordered Un-Park.\n"; static const char vldesc[] = "Audibly list the slot number of all the calls in press * to unpark it.\n"; struct valetparkeduser { struct cw_channel *chan; struct timeval start; int valetparkingnum; /* Where to go if our valetparking time expires */ char context[CW_MAX_EXTENSION]; char exten[CW_MAX_EXTENSION]; char lotname[CW_MAX_EXTENSION]; char channame[CW_MAX_EXTENSION]; int priority; int valetparkingtime; int old; struct valetparkeduser *next; }; static struct valetparkeduser *valetparkinglot; CW_MUTEX_DEFINE_STATIC(valetparking_lock); static pthread_t valetparking_thread; static int valetparking_say(struct cw_channel *chan,char *lotname) { struct valetparkeduser *cur; int x=0,y=0,res=0; int list[1024]; if(!lotname) return 0; cw_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) if(cur->lotname && !strcmp(lotname,cur->lotname)) list[y++] = cur->valetparkingnum; cw_mutex_unlock(&valetparking_lock); for(x=0;xlanguage); res = cw_waitfordigit(chan,1500); if(res != 0) { res = list[x]; break; } } return res; } static int cw_pop_valetparking_top(char *lotname) { struct valetparkeduser *cur; cw_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) if(cur->lotname && !strcmp(lotname,cur->lotname)) break; cw_mutex_unlock(&valetparking_lock); return cur ? cur->valetparkingnum : 0; } static int cw_pop_valetparking_bot(char *lotname) { struct valetparkeduser *cur,*last=NULL; cw_mutex_lock(&valetparking_lock); for(cur = valetparkinglot;cur;cur = cur->next) { if(cur->lotname && !strcmp(lotname,cur->lotname)) { last = cur; } } cw_mutex_unlock(&valetparking_lock); return last ? last->valetparkingnum : 0; } static int cw_is_valetparked(char *exten,char *lotname) { struct valetparkeduser *cur; int ext=0; int ret = 0; ext = atoi(exten); if(! ext > 0) { return ret; } cw_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; } cw_mutex_unlock(&valetparking_lock); return ret; } static int cw_valetpark_call(struct cw_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; struct cw_var_t *var; int x; int ret = -1; x = *extout; pu = calloc(1, sizeof(struct valetparkeduser)); if (pu) { cw_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) { char lastname[256]; chan->appl = "Valet Parked Call"; pu->chan = chan; /* Start music on hold */ cw_moh_start(pu->chan, cw_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->proc_context)) { strncpy(pu->context, chan->proc_context, sizeof(pu->context)-1); } else { strncpy(pu->context, chan->context, sizeof(pu->context)-1); } if (strlen(chan->proc_exten)) { strncpy(pu->exten, chan->proc_exten, sizeof(pu->exten)-1); } else { strncpy(pu->exten, chan->exten, sizeof(pu->exten)-1); } if (chan->proc_priority) { pu->priority = chan->proc_priority; } else { pu->priority = chan->priority; } pu->next = valetparkinglot; valetparkinglot = pu; cw_mutex_unlock(&valetparking_lock); if ((var = pbx_builtin_getvar_helper(chan, CW_KEYWORD_BLINDTRANSFER, "BLINDTRANSFER"))) cw_object_put(var); if (chan && !var) { time_t now = 0, then = 0; time(&then); cw_moh_stop(chan); strncpy(lastname, chan->name, sizeof(lastname) - 1); then -= 2; while(chan && !cw_check_hangup(chan) && !strcmp(chan->name, lastname)) { time(&now); if (now - then > 2) { if(!cw_streamfile(chan, "vm-extension", chan->language)) { if (!cw_waitstream(chan, "")) { cw_say_digits(chan, pu->valetparkingnum, "", chan->language); } } time(&then); } cw_safe_sleep(chan, 100); } } /* Wake up the (presumably select()ing) thread */ pthread_kill(valetparking_thread, SIGURG); if (option_verbose > 1) cw_verbose(VERBOSE_PREFIX_2 "Valet Parked %s on slot %d\n", pu->chan->name, pu->valetparkingnum); pbx_builtin_setvar_helper(pu->chan,"Parker","Yes"); cw_manager_event(EVENT_FLAG_CALL, "VirtualValetparkedCall", 6, cw_msg_tuple("Exten", "%d", pu->valetparkingnum), cw_msg_tuple("Channel", "%s", pu->chan->name), cw_msg_tuple("LotName", "%s", lotname), cw_msg_tuple("Timeout", "%ld", (long)pu->start.tv_sec + (long)(pu->valetparkingtime/1000) - (long)time(NULL)), cw_msg_tuple("CallerID", "%s", (pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "")), cw_msg_tuple("CallerIDName", "%s", (pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "")) ); cw_device_state_changed("Valet/%d@%s", pu->valetparkingnum, lotname); ret = 0; } else { cw_log(CW_LOG_WARNING, "No more valetparking spaces\n"); free(pu); cw_mutex_unlock(&valetparking_lock); } } else { cw_log(CW_LOG_WARNING, "Out of memory\n"); } return ret; } static int cw_masq_valetpark_call(struct cw_channel *rchan,int timeout, int *extout,char *lotname) { struct cw_channel *chan; struct cw_frame *f; /* Make a new, fake channel that we'll use to masquerade in the real one */ if ((chan = cw_channel_alloc(0, "ValetParked/%s", rchan->name))) { /* Make formats okay */ chan->readformat = rchan->readformat; chan->writeformat = rchan->writeformat; cw_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 = cw_read(chan))) cw_fr_free(f); cw_valetpark_call(chan, timeout, extout, lotname); return 0; } return -1; } static void *do_valetparking_thread(void *ignore) { fd_set rfds, efds; fd_set nrfds, nefds; struct timeval tv; struct cw_var_t *var; struct valetparkeduser *pu, *pl, *pt = NULL; struct cw_frame *f; int ms, tms, max; int x; int gc=0; CW_UNUSED(ignore); FD_ZERO(&rfds); FD_ZERO(&efds); for (;;) { ms = -1; max = -1; cw_mutex_lock(&valetparking_lock); pl = NULL; pu = valetparkinglot; gettimeofday(&tv, NULL); FD_ZERO(&nrfds); FD_ZERO(&nefds); while(pu) { if (!pu->old && (var = pbx_builtin_getvar_helper(pu->chan, CW_KEYWORD_BLINDTRANSFER, "BLINDTRANSFER"))) { cw_object_put(var); gc = 0; cw_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 && !cw_generator_is_active(pu->chan)) { gc++; cw_moh_start(pu->chan, cw_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 */ cw_moh_stop(pu->chan); /* Start up the PBX, or hang them up */ if (cw_pbx_start(pu->chan)) { cw_log(CW_LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", pu->chan->name); cw_hangup(pu->chan); } /* And take them out of the valetparking lot */ if (pl) pl->next = pu->next; else valetparkinglot = pu->next; pt = pu; cw_device_state_changed("Valet/%d@%s", pu->valetparkingnum, pu->lotname); 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)) cw_set_flag(pu->chan, CW_FLAG_EXCEPTION); pu->chan->fdno = x; /* See if they need servicing */ f = cw_read(pu->chan); if (!f || ((f->frametype == CW_FRAME_CONTROL) && (f->subclass == CW_CONTROL_HANGUP))) { /* There's a problem, hang them up*/ if (option_verbose > 1) cw_verbose(VERBOSE_PREFIX_2 "%s got tired of being Valet Parked\n", pu->chan->name); cw_hangup(pu->chan); /* And take them out of the valetparking lot */ if (pl) pl->next = pu->next; else valetparkinglot = pu->next; pt = pu; cw_device_state_changed("Valet/%d@%s", pu->valetparkingnum, pu->lotname); pu = pu->next; free(pt); break; } else { /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */ cw_fr_free(f); goto std; /* XXX Ick: jumping into an else statement??? XXX */ } } } if (x >= CW_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; } } } cw_mutex_unlock(&valetparking_lock); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); rfds = nrfds; efds = nefds; tv.tv_sec = ms / 1000; tv.tv_usec = (ms % 1000) * 1000; /* Wait for something to happen */ cw_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL); pthread_testcancel(); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); } return NULL; /* Never reached */ } static int valetpark_call(struct cw_channel *chan, int argc, char **argv, struct cw_dynstr *result) { char tmp[80]; struct localuser *u; int timeout; int ext = 0, res = 0; CW_UNUSED(result); if (argc < 2 || argc > 6 || !argv[0][0] || !argv[1][0]) return cw_function_syntax(vpcsyntax); timeout = (argc > 2 && argv[2][0] ? atoi(argv[2])*1000 : DEFAULT_VALETPARK_TIME); if (cw_is_valetparked(argv[0], argv[1])) { cw_log(CW_LOG_WARNING,"Call is already Valet Parked Here [%s]\n", argv[0]); if (cw_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->cid.cid_num)) { cw_explicit_goto_n(chan, chan->context, chan->exten, chan->priority + 100); return 0; } return -1; } LOCAL_USER_ADD(u); if (chan->_state != CW_STATE_UP) cw_answer(chan); if (!strcmp(argv[0],"auto")) ext = -1; else if (!strcmp(argv[0],"query")) { cw_waitfor(chan,-1); memset(&tmp, '\0', sizeof(tmp)); cw_streamfile(chan, "vm-extension", chan->language); res = cw_waitstream(chan, CW_DIGIT_ANY); if (res) return -1; cw_app_getdata(chan, "vm-then-pound", tmp, arraysize(tmp), 5000); if (tmp[0]) ext = atoi(tmp); } else { ext = atoi(argv[0]); } if (ext == 0) ext = -1; if (argc > 3 && argv[3][0]) strncpy(chan->exten, argv[3], sizeof(chan->exten)-1); if (argc > 4 && argv[4][0]) { chan->priority = atoi(argv[4]); if(!chan->priority) chan->priority = 1; } if (argc > 5 && argv[5][0]) strncpy(chan->context, argv[5], sizeof(chan->context)-1); cw_masq_valetpark_call(chan, timeout, &ext, argv[1]); LOCAL_USER_REMOVE(u); return 1; } static struct cw_channel *do_valetunpark(struct cw_channel *chan, char *exten, char *lotname) { int res=0; struct valetparkeduser *pu, *pl=NULL; int valetpark=-1; struct cw_channel *rchan = NULL; char tmp[80]; if(exten) { if(!strcmp(exten,"fifo")) { valetpark = cw_pop_valetparking_top(lotname); } else if(!strcmp(exten,"filo")) { valetpark = cw_pop_valetparking_bot(lotname); } else if(chan && !strcmp(exten,"query")) { cw_waitfor(chan,-1); memset(&tmp,0,80); cw_streamfile(chan, "vm-extension", chan->language); res = cw_waitstream(chan, CW_DIGIT_ANY); if(res) return NULL; cw_app_getdata(chan,"vm-then-pound",tmp,80,5000); if(tmp[0]) valetpark = atoi(tmp); } else { valetpark = atoi(exten); } if(valetpark == 0) { cw_log(CW_LOG_WARNING, "Nobody Valet Parked in %s",lotname); return NULL; } } cw_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; } cw_mutex_unlock(&valetparking_lock); cw_device_state_changed("Valet/%s@%s", exten, lotname); if (pu) { rchan = pu->chan; free(pu); } return rchan; } static struct cw_channel *valet_request(const char *type, int format, void *data, int *cause) { char *exten = NULL, *lotname = NULL; struct cw_channel *peer; CW_UNUSED(type); exten = cw_strdupa(data); if((lotname=strchr(exten,':'))) { *lotname = '\0'; lotname++; } if(!lotname) { cw_log(CW_LOG_WARNING,"Please specify a lotname in the dialplan."); *cause = CW_CAUSE_UNALLOCATED; return NULL; } if((peer = do_valetunpark(NULL, exten, lotname))) { if(cw_test_flag(peer, CW_FLAG_MOH)) { cw_moh_stop(peer); } if(cw_set_read_format(peer, format) || cw_set_write_format(peer, format)) { cw_log(CW_LOG_WARNING,"Hanging up on %s because I cant make it the requested format.\n",peer->name); cw_hangup(peer); *cause = CW_CAUSE_UNALLOCATED; return NULL; } /* We return the chan we have been protecting which is already up but be vewy vewy qwiet we will trick callweaver into thinking it's a new channel */ cw_setstate(peer, CW_STATE_RESERVED); } return peer; } static int valetunpark_call(struct cw_channel *chan, int argc, char **argv, struct cw_dynstr *result) { struct cw_bridge_config config; struct localuser *u; struct cw_channel *peer=NULL; int valetpark=-1; int dres; int res=0; CW_UNUSED(result); if (argc != 2 || !argv[0][0] || !argv[1][0]) return cw_function_syntax(vupsyntax); LOCAL_USER_ADD(u); if (chan->_state != CW_STATE_UP) cw_answer(chan); peer = do_valetunpark(chan, argv[0], argv[1]); if (peer) { cw_moh_stop(peer); res = cw_channel_make_compatible(chan, peer); if (res < 0) { cw_log(CW_LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name); cw_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) cw_verbose(VERBOSE_PREFIX_3 "Channel %s connected to Valet Parked call %d in lot %s\n", chan->name, valetpark, argv[1]); memset(&config,0,sizeof(struct cw_bridge_config)); cw_set_flag(&(config.features_caller) , CW_FEATURE_REDIRECT); cw_set_flag(&(config.features_callee) , CW_FEATURE_REDIRECT); res = cw_bridge_call(chan,peer,&config); if (res != CW_PBX_NO_HANGUP_PEER) cw_hangup(peer); LOCAL_USER_REMOVE(u); return res; } else { /* XXX Play a message XXX */ dres = cw_streamfile(chan, "pbx-invalidpark", chan->language); if (!dres) { dres = cw_waitstream(chan, ""); } else { cw_log(CW_LOG_WARNING, "cw_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name); res = 0; } if (option_verbose > 2) cw_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 cw_valetparking(struct cw_channel *chan, int argc, char **argv, struct cw_dynstr *result) { struct localuser *u; int res=0; CW_UNUSED(result); if (argc < 2 || argc > 6 || !argv[0][0] || !argv[1][0]) return cw_function_syntax(vpsyntax); if (!isdigit(argv[0][0])) { cw_log(CW_LOG_WARNING, "ValetParking requires a numeric extension.\n"); return -1; } LOCAL_USER_ADD(u); res = (!cw_is_valetparked(argv[0], argv[1]) ? valetpark_call(chan, argc, argv, NULL) : valetunpark_call(chan, argc, argv, NULL)); LOCAL_USER_REMOVE(u); return res; } static int valetpark_list(struct cw_channel *chan, int argc, char **argv, struct cw_dynstr *result) { char buf[64]; char *nargv[3]; struct localuser *u; int res = 0; CW_UNUSED(result); if (argc != 1 || !argv[0][0]) return cw_function_syntax(vlsyntax); LOCAL_USER_ADD(u); res = valetparking_say(chan, argv[0]); if (res > 0) { snprintf(buf, sizeof(buf), "%d", res); nargv[0] = buf; nargv[1] = argv[0]; nargv[2] = NULL; res = valetunpark_call(chan, 2, nargv, NULL); } LOCAL_USER_REMOVE(u); return 1; } static int handle_valetparkedcalls(struct cw_dynstr *ds_p, int argc, char *argv[]) { struct valetparkeduser *cur; CW_UNUSED(argc); CW_UNUSED(argv); cw_dynstr_printf(ds_p, "%4s %25s (%-15s %-12s %-4s) %-6s %-6s %-15s\n", "Num", "Channel", "Context", "Extension", "Pri", "Elapsed","Timeout","LotName"); cw_mutex_lock(&valetparking_lock); for (cur = valetparkinglot; cur; cur = cur->next) { cw_dynstr_printf(ds_p, "%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); } cw_mutex_unlock(&valetparking_lock); return RESULT_SUCCESS; } /* Pseudo-devices like Valet/@ */ static int valetparked_devicestate(void *data) { char *slot; char *lot; struct valetparkeduser *cur; int res = CW_DEVICE_INVALID; int slotnum; slot = cw_strdupa(data); if ((lot = strchr(slot, '@'))) *lot++ = 0; else return (res); slotnum = atoi(slot); if (option_debug > 2) cw_log(CW_LOG_DEBUG, "Checking device state for lot %s, slot %s\n", lot, slot); cw_mutex_lock(&valetparking_lock); cur=valetparkinglot; while(cur) { if (!strcmp(lot, cur->lotname) && slotnum == cur->valetparkingnum) res = CW_DEVICE_BUSY; cur = cur->next; } cw_mutex_unlock(&valetparking_lock); return (res); } static const char showvaletparked_help[] = "Usage: show valetparkedcalls\n" " Lists currently Valet Parked calls.\n"; static struct cw_clicmd showvaletparked = { .cmda = { "show", "valetparkedcalls", NULL }, .handler = handle_valetparkedcalls, .summary = "Lists valetparked calls", .usage = showvaletparked_help, }; /* Dump lot status */ static struct cw_manager_message *manager_valetparking_status(struct mansession *sess, const struct message *req) { struct cw_manager_message *msg; struct valetparkeduser *cur; time_t now; int err; if ((msg = cw_manager_response("Success", "Valet Parked calls will follow")) && !cw_manager_send(sess, req, &msg)) { err = 0; cw_mutex_lock(&valetparking_lock); time(&now); for (cur = valetparkinglot; !err && cur; cur = cur->next) { cw_manager_msg(&msg, 6, cw_msg_tuple("Event", "%s", "ValetParkedCall"), cw_msg_tuple("Exten", "%d", cur->valetparkingnum), cw_msg_tuple("Channel", "%s", cur->chan->name), cw_msg_tuple("Timeout", "%ld", (long)cur->start.tv_sec + (long)(cur->valetparkingtime/1000) - (long)now), cw_msg_tuple("CallerID", "%s", (cur->chan->cid.cid_num ? cur->chan->cid.cid_num : "")), cw_msg_tuple("CallerIDName", "%s", (cur->chan->cid.cid_name ? cur->chan->cid.cid_name : "")) ); err = cw_manager_send(sess, req, &msg); } cw_mutex_unlock(&valetparking_lock); if (!err) cw_manager_msg(&msg, 1, cw_msg_tuple("Event", "%s", "ValetParkedCallsComplete")); } return msg; } static struct manager_action manager_actions[] = { { .action = "ValetparkedCalls", .authority = 0, .func = manager_valetparking_status, .synopsis = "List valetparked calls", }, }; static int load_module(void) { cw_cli_register(&showvaletparked); valetparkingtime = DEFAULT_VALETPARK_TIME; cw_pthread_create(&valetparking_thread, &global_attr_default, do_valetparking_thread, NULL); valetunparkedcall_app = cw_register_function(valetunparkedcall, valetunpark_call, vupsynopsis, vupsyntax, vupdesc); valetparkedcall_app = cw_register_function(valetparkedcall, valetpark_call, vpcsynopsis, vpcsyntax, vpcdesc); valetparking_app = cw_register_function(valetparking, cw_valetparking, vpsynopsis, vpsyntax, vpdesc); valetparklist_app = cw_register_function(valetparklist,valetpark_list, vlsynopsis, vlsyntax, vldesc); cw_channel_register(&valet_tech); cw_manager_action_register_multiple(manager_actions, arraysize(manager_actions)); return 0; } static int unload_module(void) { int res = 0; if (!cw_mutex_lock(&valetparking_lock)) { if (valetparking_thread && !pthread_equal(valetparking_thread, CW_PTHREADT_STOP)) { pthread_cancel(valetparking_thread); pthread_kill(valetparking_thread, SIGURG); pthread_join(valetparking_thread, NULL); } valetparking_thread = CW_PTHREADT_STOP; cw_mutex_unlock(&valetparking_lock); } else { cw_log(CW_LOG_WARNING, "Unable to lock the valet\n"); return -1; } cw_channel_unregister(&valet_tech); cw_manager_action_unregister_multiple(manager_actions, arraysize(manager_actions)); cw_cli_unregister(&showvaletparked); res |= cw_unregister_function(valetunparkedcall_app); res |= cw_unregister_function(valetparkedcall_app); res |= cw_unregister_function(valetparking_app); res |= cw_unregister_function(valetparklist_app); return res; } MODULE_INFO(load_module, NULL, unload_module, NULL, "Valet Parking Application")