From 26b78a6b5f800cf479794a1d7af4cb6d441e5ae8 Mon Sep 17 00:00:00 2001 From: Tzafrir Cohen Date: Sun, 10 Aug 2008 04:06:56 +0300 Subject: [PATCH] holded calls: basic functionality. --- include/asterisk/features.h | 6 + res/res_features.c | 295 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) diff --git a/include/asterisk/features.h b/include/asterisk/features.h index 7072406ab..9ae0e0d0c 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -70,6 +70,12 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *host, int timeou */ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *host, int timeout, int *extout); +extern int ast_hold_call(struct ast_channel *chan, struct ast_channel *host); +extern int ast_masq_hold_call(struct ast_channel *rchan, struct ast_channel *host); +extern int ast_retrieve_call(struct ast_channel *chan, char *uniqueid); +extern int ast_retrieve_call_to_death(char *uniqueid); +extern struct ast_channel *ast_get_holded_call(char *uniqueid); + /*! \brief Determine system parking extension * Returns the call parking extension for drivers that provide special call parking help */ diff --git a/res/res_features.c b/res/res_features.c index 475c7711c..4a9d4bcda 100644 --- a/res/res_features.c +++ b/res/res_features.c @@ -69,6 +69,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define DEFAULT_FEATURE_DIGIT_TIMEOUT 500 #define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 +/*! size of the uniqueid field + * + * \todo Originally bristuff defines this one in channel.h but only uses it here. + */ +#define AST_MAX_UNIQUEID 64 + #define AST_MAX_WATCHERS 256 enum { @@ -151,12 +157,34 @@ struct parkeduser { struct parkeduser *next; }; +struct holdeduser { + struct ast_channel *chan; + struct timeval start; + int parkingnum; + int cref; + int tei; + /* Where to go if our parking time expires */ + char context[AST_MAX_EXTENSION]; + char exten[AST_MAX_EXTENSION]; + int priority; + int parkingtime; + char uniqueid[AST_MAX_UNIQUEID]; + char uniqueidpeer[AST_MAX_UNIQUEID]; + struct holdeduser *next; +}; + static struct parkeduser *parkinglot; +static struct holdeduser *holdlist; + AST_MUTEX_DEFINE_STATIC(parking_lock); /*!< protects all static variables above */ +AST_MUTEX_DEFINE_STATIC(holding_lock); + static pthread_t parking_thread; +static pthread_t holding_thread; + char *ast_parking_ext(void) { return parking_ext; @@ -2091,6 +2119,272 @@ static int park_exec(struct ast_channel *chan, void *data) return res; } +int ast_hold_call(struct ast_channel *chan, struct ast_channel *peer) +{ + /* We put the user in the parking list, then wake up the parking thread to be sure it looks + after these channels too */ + struct holdeduser *pu; + pu = malloc(sizeof(struct holdeduser)); + if (pu) { + memset(pu, 0, sizeof(pu)); + ast_mutex_lock(&holding_lock); + chan->appl = "Holded Call"; + chan->data = NULL; + + pu->chan = chan; + strncpy(pu->uniqueid, chan->uniqueid, sizeof(pu->uniqueid)); + strncpy(pu->uniqueidpeer, peer->uniqueid, sizeof(pu->uniqueidpeer)); + /* Start music on hold */ + ast_moh_start(pu->chan, NULL, NULL); + gettimeofday(&pu->start, NULL); + pu->next = holdlist; + holdlist = pu; + ast_mutex_unlock(&holding_lock); + /* Wake up the (presumably select()ing) thread */ + pthread_kill(holding_thread, SIGURG); + + manager_event(EVENT_FLAG_CALL, "HoldedCall", + "Channel1: %s\r\n" + "Channel2: %s\r\n" + "Uniqueid1: %s\r\n" + "Uniqueid2: %s\r\n" + ,pu->chan->name, peer->name, pu->chan->uniqueid, peer->uniqueid); + + } else { + ast_log(LOG_WARNING, "Out of memory\n"); + return -1; + } + return 0; +} + +int ast_masq_hold_call(struct ast_channel *rchan, struct ast_channel *peer) +{ + 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, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Onhold/%s",rchan->name); + if (chan) { + /* Let us keep track of the channel name */ + ast_string_field_build(chan, name, "Onhold/%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; + /* this might be dirty, but we need to preserve the uniqueid */ + ast_string_field_build(chan, uniqueid, "%s",rchan->uniqueid); + /* Make the masq execute */ + f = ast_read(chan); + if (f) + ast_frfree(f); + ast_hold_call(chan, peer); + return -1; + } else { + ast_log(LOG_WARNING, "Unable to create holded channel\n"); + return -1; + } + return 0; +} + +int ast_retrieve_call(struct ast_channel *chan, char *uniqueid) +{ + int res=-1, dres=-1; + struct ast_channel *peer=NULL; + struct ast_bridge_config config; + + peer = ast_get_holded_call(uniqueid); + + /* JK02: it helps to answer the channel if not already up */ + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } + + if (peer) { + ast_mutex_unlock(&peer->lock); + 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); + 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 holded call %s\n", chan->name, peer->name); + + memset(&config,0,sizeof(struct ast_bridge_config)); + ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); + ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); + 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); + 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); + dres = 0; + } + } + return res; +} + +int ast_retrieve_call_to_death(char *uniqueid) +{ + int res=-1; + struct ast_channel *peer=NULL; + + peer = ast_get_holded_call(uniqueid); + + if (peer) { + res=0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Channel %s removed from hold.\n", peer->name); + ast_mutex_unlock(&peer->lock); + ast_hangup(peer); + } else { + ast_log(LOG_WARNING, "Could not find channel with uniqueid %s to retrieve.\n", uniqueid); + } + return res; +} + +struct ast_channel *ast_get_holded_call(char *uniqueid) +{ + int res=-1; + struct ast_channel *peer=NULL; + struct holdeduser *pu, *pl=NULL; + + ast_mutex_lock(&holding_lock); + pu = holdlist; + while(pu) { + if (!strncmp(uniqueid,pu->uniqueid,sizeof(pu->uniqueid))) { + if (pl) + pl->next = pu->next; + else + holdlist = pu->next; + break; + } + pl = pu; + pu = pu->next; + } + ast_mutex_unlock(&holding_lock); + if (pu) { + peer = ast_get_channel_by_uniqueid_locked(pu->uniqueid); + free(pu); + if (peer) { + res=0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Channel %s removed from hold.\n", peer->name); + ast_moh_stop(peer); + return peer; + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Could not find channel with uniqueid %s.\n", uniqueid); + return NULL; + } + } else { + ast_log(LOG_WARNING, "Could not find held channel with uniqueid %s to retrieve.\n", uniqueid); + } + return NULL; +} + +/* this is our autmagically service thread that keeps channels onhold happy */ +static void *do_holding_thread(void *ignore) +{ + int ms, tms, max; + struct holdeduser *pu, *pl, *pt = NULL; + struct timeval tv; + struct ast_frame *f; + int x; + fd_set rfds, efds; + fd_set nrfds, nefds; + FD_ZERO(&rfds); + FD_ZERO(&efds); + for (;;) { + ms = -1; + max = -1; + ast_mutex_lock(&holding_lock); + pl = NULL; + pu = holdlist; + 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; + 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); + else + ast_clear_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 onhold\n", pu->chan->name); + ast_hangup(pu->chan); + /* find the corresponding channel and hang them up too! */ + /* but only if it is not bridged yet! */ + /* And take them out of the parking lot */ + if (pl) + pl->next = pu->next; + else + holdlist = 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(&holding_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 handle_showfeatures(int fd, int argc, char *argv[]) { int i; @@ -2554,6 +2848,7 @@ static int load_module(void) return res; ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL); + ast_pthread_create(&holding_thread, NULL, do_holding_thread, NULL); res = ast_register_application(parkedcall, park_exec, synopsis, descrip); if (!res) res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2); -- 2.11.4.GIT