Split Hold event into Hold/Unhold, and move it into core.

(closes issue ASTERISK-21487)
Review: https://reviewboard.asterisk.org/r/2565/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389746 65c4cc65-6c06-0410-ace0-fbb531ad65f3
changes/78/78/1
Jason Parker 12 years ago
parent 1223199b3d
commit 154fbf8cae

@ -96,6 +96,10 @@ AMI (Asterisk Manager Interface)
BridgedChannel and BridgedUniqueid headers with the BridgeID header to BridgedChannel and BridgedUniqueid headers with the BridgeID header to
indicate what bridge the channel is currently in. indicate what bridge the channel is currently in.
* The AMI 'Hold' event has been moved out of individual channel drivers, into
core, and is now two events: Hold and Unhold. The status field has been
removed.
Channel Drivers Channel Drivers
------------------ ------------------
* When a channel driver is configured to enable jiterbuffers, they are now * When a channel driver is configured to enable jiterbuffers, they are now

@ -6560,7 +6560,7 @@ static int dahdi_hangup(struct ast_channel *ast)
p->owner = p->subs[SUB_REAL].owner; p->owner = p->subs[SUB_REAL].owner;
if (ast_channel_state(p->owner) != AST_STATE_UP) if (ast_channel_state(p->owner) != AST_STATE_UP)
p->subs[SUB_REAL].needanswer = 1; p->subs[SUB_REAL].needanswer = 1;
ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[SUB_REAL].owner);
} else if (p->subs[SUB_THREEWAY].dfd > -1) { } else if (p->subs[SUB_THREEWAY].dfd > -1) {
swap_subs(p, SUB_THREEWAY, SUB_REAL); swap_subs(p, SUB_THREEWAY, SUB_REAL);
unalloc_sub(p, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY);
@ -6582,9 +6582,7 @@ static int dahdi_hangup(struct ast_channel *ast)
/* This is actually part of a three way, placed on hold. Place the third part /* This is actually part of a three way, placed on hold. Place the third part
on music on hold now */ on music on hold now */
if (p->subs[SUB_THREEWAY].owner) { if (p->subs[SUB_THREEWAY].owner) {
ast_queue_control_data(p->subs[SUB_THREEWAY].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[SUB_THREEWAY].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
p->subs[SUB_THREEWAY].inthreeway = 0; p->subs[SUB_THREEWAY].inthreeway = 0;
/* Make it the call wait now */ /* Make it the call wait now */
@ -6597,9 +6595,7 @@ static int dahdi_hangup(struct ast_channel *ast)
/* The other party of the three way call is currently in a call-wait state. /* The other party of the three way call is currently in a call-wait state.
Start music on hold for them, and take the main guy out of the third call */ Start music on hold for them, and take the main guy out of the third call */
if (p->subs[SUB_CALLWAIT].owner) { if (p->subs[SUB_CALLWAIT].owner) {
ast_queue_control_data(p->subs[SUB_CALLWAIT].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[SUB_CALLWAIT].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
p->subs[SUB_CALLWAIT].inthreeway = 0; p->subs[SUB_CALLWAIT].inthreeway = 0;
} }
@ -7851,7 +7847,7 @@ static int attempt_transfer(struct dahdi_pvt *p)
if (ast_bridged_channel(p->subs[SUB_REAL].owner)) { if (ast_bridged_channel(p->subs[SUB_REAL].owner)) {
/* The three-way person we're about to transfer to could still be in MOH, so /* The three-way person we're about to transfer to could still be in MOH, so
stop it now */ stop it now */
ast_queue_control(p->subs[SUB_THREEWAY].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[SUB_THREEWAY].owner);
if (ast_channel_state(p->subs[SUB_REAL].owner) == AST_STATE_RINGING) { if (ast_channel_state(p->subs[SUB_REAL].owner) == AST_STATE_RINGING) {
ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_RINGING); ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_RINGING);
} }
@ -7867,7 +7863,7 @@ static int attempt_transfer(struct dahdi_pvt *p)
ast_channel_unlock(p->subs[SUB_THREEWAY].owner); ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
unalloc_sub(p, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY);
} else if (ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) { } else if (ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) {
ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[SUB_REAL].owner);
if (ast_channel_state(p->subs[SUB_THREEWAY].owner) == AST_STATE_RINGING) { if (ast_channel_state(p->subs[SUB_THREEWAY].owner) == AST_STATE_RINGING) {
ast_queue_control(p->subs[SUB_THREEWAY].owner, AST_CONTROL_RINGING); ast_queue_control(p->subs[SUB_THREEWAY].owner, AST_CONTROL_RINGING);
} }
@ -8535,7 +8531,7 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
/* Make sure it stops ringing */ /* Make sure it stops ringing */
dahdi_set_hook(p->subs[idx].dfd, DAHDI_OFFHOOK); dahdi_set_hook(p->subs[idx].dfd, DAHDI_OFFHOOK);
/* Okay -- probably call waiting*/ /* Okay -- probably call waiting*/
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
p->subs[idx].needunhold = 1; p->subs[idx].needunhold = 1;
break; break;
case AST_STATE_RESERVED: case AST_STATE_RESERVED:
@ -8690,14 +8686,10 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
p->cid_suppress_expire = 0; p->cid_suppress_expire = 0;
/* Start music on hold if appropriate */ /* Start music on hold if appropriate */
if (!p->subs[SUB_CALLWAIT].inthreeway) { if (!p->subs[SUB_CALLWAIT].inthreeway) {
ast_queue_control_data(p->subs[SUB_CALLWAIT].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[SUB_CALLWAIT].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
p->subs[SUB_CALLWAIT].needhold = 1; p->subs[SUB_CALLWAIT].needhold = 1;
ast_queue_control_data(p->subs[SUB_REAL].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[SUB_REAL].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
p->subs[SUB_REAL].needunhold = 1; p->subs[SUB_REAL].needunhold = 1;
} else if (!p->subs[SUB_THREEWAY].owner) { } else if (!p->subs[SUB_THREEWAY].owner) {
if (!p->threewaycalling) { if (!p->threewaycalling) {
@ -8775,9 +8767,7 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
ast_verb(3, "Started three way call on channel %d\n", p->channel); ast_verb(3, "Started three way call on channel %d\n", p->channel);
/* Start music on hold */ /* Start music on hold */
ast_queue_control_data(p->subs[SUB_THREEWAY].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[SUB_THREEWAY].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
p->subs[SUB_THREEWAY].needhold = 1; p->subs[SUB_THREEWAY].needhold = 1;
} }
ast_callid_threadstorage_auto_clean(callid, callid_created); ast_callid_threadstorage_auto_clean(callid, callid_created);
@ -8814,8 +8804,9 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
swap_subs(p, SUB_THREEWAY, SUB_REAL); swap_subs(p, SUB_THREEWAY, SUB_REAL);
otherindex = SUB_REAL; otherindex = SUB_REAL;
} }
if (p->subs[otherindex].owner) if (p->subs[otherindex].owner) {
ast_queue_control(p->subs[otherindex].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[otherindex].owner);
}
p->subs[otherindex].needunhold = 1; p->subs[otherindex].needunhold = 1;
p->owner = p->subs[SUB_REAL].owner; p->owner = p->subs[SUB_REAL].owner;
} else { } else {
@ -8823,8 +8814,9 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
swap_subs(p, SUB_THREEWAY, SUB_REAL); swap_subs(p, SUB_THREEWAY, SUB_REAL);
ast_channel_softhangup_internal_flag_add(p->subs[SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); ast_channel_softhangup_internal_flag_add(p->subs[SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV);
p->owner = p->subs[SUB_REAL].owner; p->owner = p->subs[SUB_REAL].owner;
if (p->subs[SUB_REAL].owner) if (p->subs[SUB_REAL].owner) {
ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[SUB_REAL].owner);
}
p->subs[SUB_REAL].needunhold = 1; p->subs[SUB_REAL].needunhold = 1;
dahdi_enable_ec(p); dahdi_enable_ec(p);
} }
@ -9017,8 +9009,9 @@ static struct ast_frame *__dahdi_exception(struct ast_channel *ast)
(res != DAHDI_EVENT_HOOKCOMPLETE)) { (res != DAHDI_EVENT_HOOKCOMPLETE)) {
ast_debug(1, "Restoring owner of channel %d on event %d\n", p->channel, res); ast_debug(1, "Restoring owner of channel %d on event %d\n", p->channel, res);
p->owner = p->subs[SUB_REAL].owner; p->owner = p->subs[SUB_REAL].owner;
if (p->owner) if (p->owner) {
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
}
p->subs[SUB_REAL].needunhold = 1; p->subs[SUB_REAL].needunhold = 1;
} }
switch (res) { switch (res) {
@ -9062,7 +9055,7 @@ static struct ast_frame *__dahdi_exception(struct ast_channel *ast)
p->callwaitingrepeat = 0; p->callwaitingrepeat = 0;
p->cidcwexpire = 0; p->cidcwexpire = 0;
p->cid_suppress_expire = 0; p->cid_suppress_expire = 0;
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
p->subs[SUB_REAL].needunhold = 1; p->subs[SUB_REAL].needunhold = 1;
} else } else
ast_log(LOG_WARNING, "Absorbed on hook, but nobody is left!?!?\n"); ast_log(LOG_WARNING, "Absorbed on hook, but nobody is left!?!?\n");
@ -10699,7 +10692,7 @@ static void *analog_ss_thread(void *data)
swap_subs(p, SUB_REAL, SUB_THREEWAY); swap_subs(p, SUB_REAL, SUB_THREEWAY);
unalloc_sub(p, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY);
p->owner = p->subs[SUB_REAL].owner; p->owner = p->subs[SUB_REAL].owner;
ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[SUB_REAL].owner);
ast_hangup(chan); ast_hangup(chan);
goto quit; goto quit;
} else { } else {

@ -2096,18 +2096,17 @@ static void setup_rtp_connection(unsigned call_reference, const char *remoteIp,
ast_queue_control(pvt->owner, AST_CONTROL_PROGRESS); ast_queue_control(pvt->owner, AST_CONTROL_PROGRESS);
switch (rtp_change) { switch (rtp_change) {
case NEED_HOLD: case NEED_HOLD:
ast_queue_control(pvt->owner, AST_CONTROL_HOLD); ast_queue_hold(pvt->owner, NULL);
break; break;
case NEED_UNHOLD: case NEED_UNHOLD:
ast_queue_control(pvt->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(pvt->owner);
break; break;
default: default:
break; break;
} }
ast_channel_unlock(pvt->owner); ast_channel_unlock(pvt->owner);
pvt_native = ast_format_cap_destroy(pvt_native); pvt_native = ast_format_cap_destroy(pvt_native);
} } else {
else {
if (pvt->options.progress_audio) if (pvt->options.progress_audio)
pvt->newcontrol = AST_CONTROL_PROGRESS; pvt->newcontrol = AST_CONTROL_PROGRESS;
else if (rtp_change == NEED_HOLD) else if (rtp_change == NEED_HOLD)
@ -2599,10 +2598,11 @@ static void remote_hold(unsigned call_reference, const char *token, int is_hold)
if (!pvt) if (!pvt)
return; return;
if (pvt->owner && !ast_channel_trylock(pvt->owner)) { if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
if (is_hold) if (is_hold) {
ast_queue_control(pvt->owner, AST_CONTROL_HOLD); ast_queue_hold(pvt->owner, NULL);
else } else {
ast_queue_control(pvt->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(pvt->owner);
}
ast_channel_unlock(pvt->owner); ast_channel_unlock(pvt->owner);
} }
else { else {

@ -3095,9 +3095,9 @@ static int iax2_queue_frame(int callno, struct ast_frame *f)
} }
/*! /*!
* \brief Queue a hangup frame on the ast_channel owner * \brief Queue a hold frame on the ast_channel owner
* *
* This function queues a hangup frame on the owner of the IAX2 pvt struct that * This function queues a hold frame on the owner of the IAX2 pvt struct that
* is active for the given call number. * is active for the given call number.
* *
* \pre Assumes lock for callno is already held. * \pre Assumes lock for callno is already held.
@ -3107,20 +3107,20 @@ static int iax2_queue_frame(int callno, struct ast_frame *f)
* This function may unlock and lock the mutex associated with this callno, * This function may unlock and lock the mutex associated with this callno,
* meaning that another thread may grab it and destroy the call. * meaning that another thread may grab it and destroy the call.
*/ */
static int iax2_queue_hangup(int callno) static int iax2_queue_hold(int callno, const char *musicclass)
{ {
iax2_lock_owner(callno); iax2_lock_owner(callno);
if (iaxs[callno] && iaxs[callno]->owner) { if (iaxs[callno] && iaxs[callno]->owner) {
ast_queue_hangup(iaxs[callno]->owner); ast_queue_hold(iaxs[callno]->owner, musicclass);
ast_channel_unlock(iaxs[callno]->owner); ast_channel_unlock(iaxs[callno]->owner);
} }
return 0; return 0;
} }
/*! /*!
* \brief Queue a control frame on the ast_channel owner * \brief Queue an unhold frame on the ast_channel owner
* *
* This function queues a control frame on the owner of the IAX2 pvt struct that * This function queues an unhold frame on the owner of the IAX2 pvt struct that
* is active for the given call number. * is active for the given call number.
* *
* \pre Assumes lock for callno is already held. * \pre Assumes lock for callno is already held.
@ -3130,12 +3130,34 @@ static int iax2_queue_hangup(int callno)
* This function may unlock and lock the mutex associated with this callno, * This function may unlock and lock the mutex associated with this callno,
* meaning that another thread may grab it and destroy the call. * meaning that another thread may grab it and destroy the call.
*/ */
static int iax2_queue_control_data(int callno, static int iax2_queue_unhold(int callno)
enum ast_control_frame_type control, const void *data, size_t datalen)
{ {
iax2_lock_owner(callno); iax2_lock_owner(callno);
if (iaxs[callno] && iaxs[callno]->owner) { if (iaxs[callno] && iaxs[callno]->owner) {
ast_queue_control_data(iaxs[callno]->owner, control, data, datalen); ast_queue_unhold(iaxs[callno]->owner);
ast_channel_unlock(iaxs[callno]->owner);
}
return 0;
}
/*!
* \brief Queue a hangup frame on the ast_channel owner
*
* This function queues a hangup frame on the owner of the IAX2 pvt struct that
* is active for the given call number.
*
* \pre Assumes lock for callno is already held.
*
* \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno]
* was valid before calling it, it may no longer be valid after calling it.
* This function may unlock and lock the mutex associated with this callno,
* meaning that another thread may grab it and destroy the call.
*/
static int iax2_queue_hangup(int callno)
{
iax2_lock_owner(callno);
if (iaxs[callno] && iaxs[callno]->owner) {
ast_queue_hangup(iaxs[callno]->owner);
ast_channel_unlock(iaxs[callno]->owner); ast_channel_unlock(iaxs[callno]->owner);
} }
return 0; return 0;
@ -10302,16 +10324,6 @@ static int socket_process_helper(struct iax2_thread *thread)
break; break;
case IAX_COMMAND_QUELCH: case IAX_COMMAND_QUELCH:
if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) { if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) {
/* Generate Manager Hold event, if necessary*/
if (iaxs[fr->callno]->owner) {
ast_manager_event(iaxs[fr->callno]->owner, EVENT_FLAG_CALL, "Hold",
"Status: On\r\n"
"Channel: %s\r\n"
"Uniqueid: %s\r\n",
ast_channel_name(iaxs[fr->callno]->owner),
ast_channel_uniqueid(iaxs[fr->callno]->owner));
}
ast_set_flag64(iaxs[fr->callno], IAX_QUELCH); ast_set_flag64(iaxs[fr->callno], IAX_QUELCH);
if (ies.musiconhold) { if (ies.musiconhold) {
const char *moh_suggest; const char *moh_suggest;
@ -10326,9 +10338,7 @@ static int socket_process_helper(struct iax2_thread *thread)
* need to check iaxs[fr->callno] after it returns. * need to check iaxs[fr->callno] after it returns.
*/ */
moh_suggest = iaxs[fr->callno]->mohsuggest; moh_suggest = iaxs[fr->callno]->mohsuggest;
iax2_queue_control_data(fr->callno, AST_CONTROL_HOLD, iax2_queue_hold(fr->callno, moh_suggest);
S_OR(moh_suggest, NULL),
!ast_strlen_zero(moh_suggest) ? strlen(moh_suggest) + 1 : 0);
ast_channel_unlock(iaxs[fr->callno]->owner); ast_channel_unlock(iaxs[fr->callno]->owner);
} }
} }
@ -10339,15 +10349,6 @@ static int socket_process_helper(struct iax2_thread *thread)
if (!iaxs[fr->callno]) { if (!iaxs[fr->callno]) {
break; break;
} }
/* Generate Manager Unhold event, if necessary */
if (iaxs[fr->callno]->owner && ast_test_flag64(iaxs[fr->callno], IAX_QUELCH)) {
ast_manager_event(iaxs[fr->callno]->owner, EVENT_FLAG_CALL, "Hold",
"Status: Off\r\n"
"Channel: %s\r\n"
"Uniqueid: %s\r\n",
ast_channel_name(iaxs[fr->callno]->owner),
ast_channel_uniqueid(iaxs[fr->callno]->owner));
}
ast_clear_flag64(iaxs[fr->callno], IAX_QUELCH); ast_clear_flag64(iaxs[fr->callno], IAX_QUELCH);
if (!iaxs[fr->callno]->owner) { if (!iaxs[fr->callno]->owner) {
@ -10358,7 +10359,7 @@ static int socket_process_helper(struct iax2_thread *thread)
* We already hold the owner lock so we do not * We already hold the owner lock so we do not
* need to check iaxs[fr->callno] after it returns. * need to check iaxs[fr->callno] after it returns.
*/ */
iax2_queue_control_data(fr->callno, AST_CONTROL_UNHOLD, NULL, 0); iax2_queue_unhold(fr->callno);
ast_channel_unlock(iaxs[fr->callno]->owner); ast_channel_unlock(iaxs[fr->callno]->owner);
} }
break; break;

@ -3229,7 +3229,7 @@ static int attempt_transfer(struct mgcp_endpoint *p, struct mgcp_subchannel *sub
enum ast_transfer_result res; enum ast_transfer_result res;
/* Ensure that the other channel goes off hold and that it is indicating properly */ /* Ensure that the other channel goes off hold and that it is indicating properly */
ast_queue_control(sub->next->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub->next->owner);
if (ast_channel_state(sub->owner) == AST_STATE_RINGING) { if (ast_channel_state(sub->owner) == AST_STATE_RINGING) {
ast_queue_control(sub->next->owner, AST_CONTROL_RINGING); ast_queue_control(sub->next->owner, AST_CONTROL_RINGING);
} }
@ -3275,7 +3275,7 @@ static void handle_hd_hf(struct mgcp_subchannel *sub, char *ev)
if (sub->outgoing) { if (sub->outgoing) {
/* Answered */ /* Answered */
if (sub->owner) { if (sub->owner) {
ast_queue_control(sub->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub->owner);
sub->cxmode = MGCP_CX_SENDRECV; sub->cxmode = MGCP_CX_SENDRECV;
if (!sub->rtp) { if (!sub->rtp) {
start_rtp(sub); start_rtp(sub);
@ -3331,7 +3331,7 @@ static void handle_hd_hf(struct mgcp_subchannel *sub, char *ev)
ast_log(LOG_WARNING, "On hook, but already have owner on %s@%s\n", p->name, p->parent->name); ast_log(LOG_WARNING, "On hook, but already have owner on %s@%s\n", p->name, p->parent->name);
ast_log(LOG_WARNING, "If we're onhook why are we here trying to handle a hd or hf?\n"); ast_log(LOG_WARNING, "If we're onhook why are we here trying to handle a hd or hf?\n");
} }
ast_queue_control(sub->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub->owner);
sub->cxmode = MGCP_CX_SENDRECV; sub->cxmode = MGCP_CX_SENDRECV;
if (!sub->rtp) { if (!sub->rtp) {
start_rtp(sub); start_rtp(sub);
@ -3448,8 +3448,9 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req,
sub->cxmode = MGCP_CX_MUTE; sub->cxmode = MGCP_CX_MUTE;
ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name); ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name);
transmit_modify_request(sub); transmit_modify_request(sub);
if (sub->owner) if (sub->owner) {
ast_queue_control(sub->owner, AST_CONTROL_HOLD); ast_queue_hold(sub->owner, NULL);
}
sub->next->cxmode = MGCP_CX_RECVONLY; sub->next->cxmode = MGCP_CX_RECVONLY;
handle_hd_hf(sub->next, ev); handle_hd_hf(sub->next, ev);
} else if (sub->owner && sub->next->owner) { } else if (sub->owner && sub->next->owner) {
@ -3460,7 +3461,7 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req,
sub->id, sub->next->id, p->name, p->parent->name); sub->id, sub->next->id, p->name, p->parent->name);
sub->cxmode = MGCP_CX_CONF; sub->cxmode = MGCP_CX_CONF;
sub->next->cxmode = MGCP_CX_CONF; sub->next->cxmode = MGCP_CX_CONF;
ast_queue_control(sub->next->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub->next->owner);
transmit_modify_request(sub); transmit_modify_request(sub);
transmit_modify_request(sub->next); transmit_modify_request(sub->next);
} else { } else {
@ -3473,8 +3474,8 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req,
ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name); ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name);
transmit_modify_request(sub); transmit_modify_request(sub);
ast_queue_control(sub->owner, AST_CONTROL_HOLD); ast_queue_hold(sub->owner, NULL);
ast_queue_control(sub->next->owner, AST_CONTROL_HOLD); ast_queue_hold(sub->next->owner, NULL);
handle_hd_hf(sub->next, ev); handle_hd_hf(sub->next, ev);
} }
@ -3489,7 +3490,7 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req,
/* XXX - What do we do now? */ /* XXX - What do we do now? */
return -1; return -1;
} }
ast_queue_control(p->sub->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->sub->owner);
p->sub->cxmode = MGCP_CX_SENDRECV; p->sub->cxmode = MGCP_CX_SENDRECV;
transmit_modify_request(p->sub); transmit_modify_request(p->sub);
} }

@ -10943,7 +10943,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
ch->hold.port = 0; ch->hold.port = 0;
ch->hold.channel = 0; ch->hold.channel = 0;
ast_queue_control(ch->ast, AST_CONTROL_UNHOLD); ast_queue_unhold(ch->ast);
if (misdn_lib_send_event(bc, EVENT_RETRIEVE_ACKNOWLEDGE) < 0) { if (misdn_lib_send_event(bc, EVENT_RETRIEVE_ACKNOWLEDGE) < 0) {
chan_misdn_log(4, bc->port, " --> RETRIEVE_ACK failed\n"); chan_misdn_log(4, bc->port, " --> RETRIEVE_ACK failed\n");
@ -10973,7 +10973,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
ch->hold.port = bc->port; ch->hold.port = bc->port;
ch->hold.channel = bc->channel; ch->hold.channel = bc->channel;
ast_queue_control(ch->ast, AST_CONTROL_HOLD); ast_queue_hold(ch->ast, NULL);
misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE); misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE);
} else { } else {

@ -2481,9 +2481,9 @@ static void jingle_action_session_info(struct jingle_endpoint *endpoint, struct
ast_setstate(chan, AST_STATE_RINGING); ast_setstate(chan, AST_STATE_RINGING);
} }
} else if (iks_find_with_attrib(pak->query, "hold", "xmlns", JINGLE_RTP_INFO_NS)) { } else if (iks_find_with_attrib(pak->query, "hold", "xmlns", JINGLE_RTP_INFO_NS)) {
ast_queue_control(chan, AST_CONTROL_HOLD); ast_queue_hold(chan, NULL);
} else if (iks_find_with_attrib(pak->query, "unhold", "xmlns", JINGLE_RTP_INFO_NS)) { } else if (iks_find_with_attrib(pak->query, "unhold", "xmlns", JINGLE_RTP_INFO_NS)) {
ast_queue_control(chan, AST_CONTROL_UNHOLD); ast_queue_unhold(chan);
} }
ast_channel_unlock(chan); ast_channel_unlock(chan);

@ -9879,16 +9879,9 @@ static int find_sdp(struct sip_request *req)
/*! \brief Change hold state for a call */ /*! \brief Change hold state for a call */
static void change_hold_state(struct sip_pvt *dialog, struct sip_request *req, int holdstate, int sendonly) static void change_hold_state(struct sip_pvt *dialog, struct sip_request *req, int holdstate, int sendonly)
{ {
if (sip_cfg.notifyhold && (!holdstate || !ast_test_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD))) if (sip_cfg.notifyhold && (!holdstate || !ast_test_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD))) {
sip_peer_hold(dialog, holdstate); sip_peer_hold(dialog, holdstate);
if (sip_cfg.callevents) }
manager_event(EVENT_FLAG_CALL, "Hold",
"Status: %s\r\n"
"Channel: %s\r\n"
"Uniqueid: %s\r\n",
holdstate ? "On" : "Off",
ast_channel_name(dialog->owner),
ast_channel_uniqueid(dialog->owner));
append_history(dialog, holdstate ? "Hold" : "Unhold", "%s", ast_str_buffer(req->data)); append_history(dialog, holdstate ? "Hold" : "Unhold", "%s", ast_str_buffer(req->data));
if (!holdstate) { /* Put off remote hold */ if (!holdstate) { /* Put off remote hold */
ast_clear_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD); /* Clear both flags */ ast_clear_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD); /* Clear both flags */
@ -10795,16 +10788,14 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) && (!ast_sockaddr_isnull(sa) || !ast_sockaddr_isnull(vsa) || !ast_sockaddr_isnull(tsa) || !ast_sockaddr_isnull(isa)) && (!sendonly || sendonly == -1)) { if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) && (!ast_sockaddr_isnull(sa) || !ast_sockaddr_isnull(vsa) || !ast_sockaddr_isnull(tsa) || !ast_sockaddr_isnull(isa)) && (!sendonly || sendonly == -1)) {
if (!ast_test_flag(&p->flags[2], SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL)) { if (!ast_test_flag(&p->flags[2], SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL)) {
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
} }
/* Activate a re-invite */ /* Activate a re-invite */
ast_queue_frame(p->owner, &ast_null_frame); ast_queue_frame(p->owner, &ast_null_frame);
change_hold_state(p, req, FALSE, sendonly); change_hold_state(p, req, FALSE, sendonly);
} else if ((sockaddr_is_null_or_any(sa) && sockaddr_is_null_or_any(vsa) && sockaddr_is_null_or_any(tsa) && sockaddr_is_null_or_any(isa)) || (sendonly && sendonly != -1)) { } else if ((sockaddr_is_null_or_any(sa) && sockaddr_is_null_or_any(vsa) && sockaddr_is_null_or_any(tsa) && sockaddr_is_null_or_any(isa)) || (sendonly && sendonly != -1)) {
if (!ast_test_flag(&p->flags[2], SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL)) { if (!ast_test_flag(&p->flags[2], SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL)) {
ast_queue_control_data(p->owner, AST_CONTROL_HOLD, ast_queue_hold(p->owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
if (sendonly) if (sendonly)
ast_rtp_instance_stop(p->rtp); ast_rtp_instance_stop(p->rtp);
@ -25440,7 +25431,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
*without* an SDP, which is supposed to mean "Go back to your state" *without* an SDP, which is supposed to mean "Go back to your state"
and since they put os on remote hold, we go back to off hold */ and since they put os on remote hold, we go back to off hold */
if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) { if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
/* Activate a re-invite */ /* Activate a re-invite */
ast_queue_frame(p->owner, &ast_null_frame); ast_queue_frame(p->owner, &ast_null_frame);
change_hold_state(p, req, FALSE, 0); change_hold_state(p, req, FALSE, 0);
@ -26703,7 +26694,7 @@ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
bridged_to = ast_bridged_channel(c); bridged_to = ast_bridged_channel(c);
if (bridged_to) { if (bridged_to) {
/* Don't actually hangup here... */ /* Don't actually hangup here... */
ast_queue_control(c, AST_CONTROL_UNHOLD); ast_queue_unhold(c);
ast_channel_unlock(c); /* async_goto can do a masquerade, no locks can be held during a masq */ ast_channel_unlock(c); /* async_goto can do a masquerade, no locks can be held during a masq */
ast_async_goto(bridged_to, p->context, p->refer->refer_to, 1); ast_async_goto(bridged_to, p->context, p->refer->refer_to, 1);
ast_channel_lock(c); ast_channel_lock(c);

@ -5263,7 +5263,7 @@ static int skinny_transfer(struct skinny_subchannel *sub)
ast_channel_name(xferor->owner), ast_bridged_channel(xferor->owner) ? ast_channel_name(ast_bridged_channel(xferor->owner)) : ""); ast_channel_name(xferor->owner), ast_bridged_channel(xferor->owner) ? ast_channel_name(ast_bridged_channel(xferor->owner)) : "");
if (ast_bridged_channel(xferor->owner)) { if (ast_bridged_channel(xferor->owner)) {
ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(xferee->owner);
if (ast_channel_state(xferor->owner) == AST_STATE_RING) { if (ast_channel_state(xferor->owner) == AST_STATE_RING) {
/* play ringing inband */ /* play ringing inband */
if ((ts = ast_get_indication_tone(ast_channel_zone(xferor->owner), "ring"))) { if ((ts = ast_get_indication_tone(ast_channel_zone(xferor->owner), "ring"))) {
@ -5279,7 +5279,7 @@ static int skinny_transfer(struct skinny_subchannel *sub)
return -1; return -1;
} }
} else if (ast_bridged_channel(xferee->owner)) { } else if (ast_bridged_channel(xferee->owner)) {
ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(xferee->owner);
if (ast_channel_state(xferor->owner) == AST_STATE_RING) { if (ast_channel_state(xferor->owner) == AST_STATE_RING) {
/* play ringing inband */ /* play ringing inband */
if ((ts = ast_get_indication_tone(ast_channel_zone(xferor->owner), "ring"))) { if ((ts = ast_get_indication_tone(ast_channel_zone(xferor->owner), "ring"))) {
@ -5690,9 +5690,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
sub->substate = SUBSTATE_HOLD; sub->substate = SUBSTATE_HOLD;
ast_queue_control_data(sub->owner, AST_CONTROL_HOLD, ast_queue_hold(sub->owner, l->mohsuggest);
S_OR(l->mohsuggest, NULL),
!ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0);
return; return;
default: default:
@ -5874,7 +5872,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK); transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK);
} }
if (sub->substate == SUBSTATE_HOLD) { if (sub->substate == SUBSTATE_HOLD) {
ast_queue_control(sub->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub->owner);
transmit_connect(d, sub); transmit_connect(d, sub);
} }
transmit_ringer_mode(d, SKINNY_RING_OFF); transmit_ringer_mode(d, SKINNY_RING_OFF);
@ -5952,9 +5950,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_HOLD from %s (on call-%d)\n", substate2str(sub->substate), sub->callid); ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_HOLD from %s (on call-%d)\n", substate2str(sub->substate), sub->callid);
return; return;
} }
ast_queue_control_data(sub->owner, AST_CONTROL_HOLD, ast_queue_hold(sub->owner, l->mohsuggest);
S_OR(l->mohsuggest, NULL),
!ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0);
transmit_activatecallplane(d, l); transmit_activatecallplane(d, l);
transmit_closereceivechannel(d, sub); transmit_closereceivechannel(d, sub);

@ -2453,7 +2453,7 @@ static void sub_hold(struct unistimsession *pte, struct unistim_subchannel *sub)
send_select_output(pte, pte->device->output, pte->device->volume, MUTE_ON); send_select_output(pte, pte->device->output, pte->device->volume, MUTE_ON);
send_stop_timer(pte); send_stop_timer(pte);
if (sub->owner) { if (sub->owner) {
ast_queue_control_data(sub->owner, AST_CONTROL_HOLD, NULL, 0); ast_queue_hold(sub->owner, NULL);
send_end_call(pte); send_end_call(pte);
} }
return; return;
@ -2474,7 +2474,7 @@ static void sub_unhold(struct unistimsession *pte, struct unistim_subchannel *su
send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF); send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF);
send_start_timer(pte); send_start_timer(pte);
if (sub->owner) { if (sub->owner) {
ast_queue_control_data(sub->owner, AST_CONTROL_UNHOLD, NULL, 0); ast_queue_unhold(sub->owner);
if (sub->rtp) { if (sub->rtp) {
send_start_rtp(sub); send_start_rtp(sub);
} }
@ -2961,8 +2961,7 @@ static void transfer_call_step1(struct unistimsession *pte)
if (sub->moh) { if (sub->moh) {
ast_log(LOG_WARNING, "Transfer with peer already listening music on hold\n"); ast_log(LOG_WARNING, "Transfer with peer already listening music on hold\n");
} else { } else {
ast_queue_control_data(sub->owner, AST_CONTROL_HOLD, ast_queue_hold(sub->owner, sub->parent->musicclass);
sub->parent->musicclass, strlen(sub->parent->musicclass) + 1);
sub->moh = 1; sub->moh = 1;
sub->subtype = SUB_THREEWAY; sub->subtype = SUB_THREEWAY;
} }
@ -2988,7 +2987,7 @@ static void transfer_cancel_step2(struct unistimsession *pte)
} }
if (sub->owner) { if (sub->owner) {
swap_subs(sub, sub_trans); swap_subs(sub, sub_trans);
ast_queue_control(sub_trans->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub_trans->owner);
sub_trans->moh = 0; sub_trans->moh = 0;
sub_trans->subtype = SUB_REAL; sub_trans->subtype = SUB_REAL;
sub->subtype = SUB_THREEWAY; sub->subtype = SUB_THREEWAY;
@ -3498,7 +3497,7 @@ static void key_dial_page(struct unistimsession *pte, char keycode)
if (sub && sub->owner) { if (sub && sub->owner) {
sub_stop_silence(pte, sub); sub_stop_silence(pte, sub);
send_tone(pte, 0, 0); send_tone(pte, 0, 0);
ast_queue_control(sub->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub->owner);
sub->moh = 0; sub->moh = 0;
sub->subtype = SUB_REAL; sub->subtype = SUB_REAL;
pte->state = STATE_CALL; pte->state = STATE_CALL;
@ -4789,7 +4788,7 @@ static int unistim_hangup(struct ast_channel *ast)
if (unistimdebug) { if (unistimdebug) {
ast_verb(0, "Threeway call disconnected, switching to real call\n"); ast_verb(0, "Threeway call disconnected, switching to real call\n");
} }
ast_queue_control(sub_trans->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(sub_trans->owner);
sub_trans->moh = 0; sub_trans->moh = 0;
sub_trans->subtype = SUB_REAL; sub_trans->subtype = SUB_REAL;
swap_subs(sub_trans, sub); swap_subs(sub_trans, sub);

@ -1354,7 +1354,7 @@ int analog_hangup(struct analog_pvt *p, struct ast_channel *ast)
if (ast_channel_state(p->owner) != AST_STATE_UP) { if (ast_channel_state(p->owner) != AST_STATE_UP) {
ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_ANSWER); ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_ANSWER);
} }
ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner);
/* Unlock the call-waiting call that we swapped to real-call. */ /* Unlock the call-waiting call that we swapped to real-call. */
ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner);
} else if (p->subs[ANALOG_SUB_THREEWAY].allocd) { } else if (p->subs[ANALOG_SUB_THREEWAY].allocd) {
@ -1382,9 +1382,7 @@ int analog_hangup(struct analog_pvt *p, struct ast_channel *ast)
/* This is actually part of a three way, placed on hold. Place the third part /* This is actually part of a three way, placed on hold. Place the third part
on music on hold now */ on music on hold now */
if (p->subs[ANALOG_SUB_THREEWAY].owner) { if (p->subs[ANALOG_SUB_THREEWAY].owner) {
ast_queue_control_data(p->subs[ANALOG_SUB_THREEWAY].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[ANALOG_SUB_THREEWAY].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 0); analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 0);
/* Make it the call wait now */ /* Make it the call wait now */
@ -1406,9 +1404,7 @@ int analog_hangup(struct analog_pvt *p, struct ast_channel *ast)
Start music on hold for them, and take the main guy out of the third call */ Start music on hold for them, and take the main guy out of the third call */
analog_set_inthreeway(p, ANALOG_SUB_CALLWAIT, 0); analog_set_inthreeway(p, ANALOG_SUB_CALLWAIT, 0);
if (p->subs[ANALOG_SUB_CALLWAIT].owner) { if (p->subs[ANALOG_SUB_CALLWAIT].owner) {
ast_queue_control_data(p->subs[ANALOG_SUB_CALLWAIT].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[ANALOG_SUB_CALLWAIT].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
} }
if (p->subs[ANALOG_SUB_CALLWAIT].owner) { if (p->subs[ANALOG_SUB_CALLWAIT].owner) {
@ -2323,7 +2319,7 @@ static void *__analog_ss_thread(void *data)
analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_THREEWAY); analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_THREEWAY);
analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY);
analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner);
ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner);
ast_hangup(chan); ast_hangup(chan);
goto quit; goto quit;
} else { } else {
@ -3024,7 +3020,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
/* Make sure it stops ringing */ /* Make sure it stops ringing */
analog_off_hook(p); analog_off_hook(p);
/* Okay -- probably call waiting */ /* Okay -- probably call waiting */
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
break; break;
case AST_STATE_RESERVED: case AST_STATE_RESERVED:
/* Start up dialtone */ /* Start up dialtone */
@ -3183,14 +3179,10 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
/* Start music on hold if appropriate */ /* Start music on hold if appropriate */
if (!p->subs[ANALOG_SUB_CALLWAIT].inthreeway) { if (!p->subs[ANALOG_SUB_CALLWAIT].inthreeway) {
ast_queue_control_data(p->subs[ANALOG_SUB_CALLWAIT].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[ANALOG_SUB_CALLWAIT].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
ast_queue_control_data(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[ANALOG_SUB_REAL].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL), ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner);
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_UNHOLD);
/* Unlock the call-waiting call that we swapped to real-call. */ /* Unlock the call-waiting call that we swapped to real-call. */
ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner);
@ -3282,9 +3274,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
ast_verb(3, "Started three way call on channel %d\n", p->channel); ast_verb(3, "Started three way call on channel %d\n", p->channel);
/* Start music on hold */ /* Start music on hold */
ast_queue_control_data(p->subs[ANALOG_SUB_THREEWAY].owner, AST_CONTROL_HOLD, ast_queue_hold(p->subs[ANALOG_SUB_THREEWAY].owner, p->mohsuggest);
S_OR(p->mohsuggest, NULL),
!ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
} }
ast_callid_threadstorage_auto_clean(callid, callid_created); ast_callid_threadstorage_auto_clean(callid, callid_created);
} }
@ -3334,7 +3324,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL);
orig_3way_sub = ANALOG_SUB_REAL; orig_3way_sub = ANALOG_SUB_REAL;
} }
ast_queue_control(p->subs[orig_3way_sub].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[orig_3way_sub].owner);
analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner);
} else { } else {
ast_verb(3, "Dumping incomplete call on %s\n", ast_channel_name(p->subs[ANALOG_SUB_THREEWAY].owner)); ast_verb(3, "Dumping incomplete call on %s\n", ast_channel_name(p->subs[ANALOG_SUB_THREEWAY].owner));
@ -3342,7 +3332,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
orig_3way_sub = ANALOG_SUB_REAL; orig_3way_sub = ANALOG_SUB_REAL;
ast_softhangup_nolock(p->subs[ANALOG_SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); ast_softhangup_nolock(p->subs[ANALOG_SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV);
analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner);
ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner);
analog_set_echocanceller(p, 1); analog_set_echocanceller(p, 1);
} }
} }
@ -3585,7 +3575,7 @@ struct ast_frame *analog_exception(struct analog_pvt *p, struct ast_channel *ast
analog_event2str(res), ast_channel_name(ast), ast_channel_name(p->owner)); analog_event2str(res), ast_channel_name(ast), ast_channel_name(p->owner));
} }
if (p->owner) { if (p->owner) {
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
} }
} }
switch (res) { switch (res) {
@ -3624,7 +3614,7 @@ struct ast_frame *analog_exception(struct analog_pvt *p, struct ast_channel *ast
ast_setstate(p->owner, AST_STATE_UP); ast_setstate(p->owner, AST_STATE_UP);
} }
analog_stop_callwait(p); analog_stop_callwait(p);
ast_queue_control(p->owner, AST_CONTROL_UNHOLD); ast_queue_unhold(p->owner);
} else { } else {
ast_log(LOG_WARNING, "Absorbed %s, but nobody is left!?!?\n", ast_log(LOG_WARNING, "Absorbed %s, but nobody is left!?!?\n",
analog_event2str(res)); analog_event2str(res));

@ -1245,6 +1245,50 @@ static void pri_queue_frame(struct sig_pri_span *pri, int chanpos, struct ast_fr
} }
} }
/*!
* \internal
* \brief Queue a hold frame onto the owner channel.
* \since 12
*
* \param pri PRI span control structure.
* \param chanpos Channel position in the span.
*
* \note Assumes the pri->lock is already obtained.
* \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained.
*
* \return Nothing
*/
static void sig_pri_queue_hold(struct sig_pri_span *pri, int chanpos)
{
sig_pri_lock_owner(pri, chanpos);
if (pri->pvts[chanpos]->owner) {
ast_queue_hold(pri->pvts[chanpos]->owner, NULL);
ast_channel_unlock(pri->pvts[chanpos]->owner);
}
}
/*!
* \internal
* \brief Queue an unhold frame onto the owner channel.
* \since 12
*
* \param pri PRI span control structure.
* \param chanpos Channel position in the span.
*
* \note Assumes the pri->lock is already obtained.
* \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained.
*
* \return Nothing
*/
static void sig_pri_queue_unhold(struct sig_pri_span *pri, int chanpos)
{
sig_pri_lock_owner(pri, chanpos);
if (pri->pvts[chanpos]->owner) {
ast_queue_unhold(pri->pvts[chanpos]->owner);
ast_channel_unlock(pri->pvts[chanpos]->owner);
}
}
/*! /*!
* \internal * \internal
* \brief Queue a control frame of the specified subclass onto the owner channel. * \brief Queue a control frame of the specified subclass onto the owner channel.
@ -5177,42 +5221,6 @@ static void sig_pri_moh_fsm_event(struct ast_channel *chan, struct sig_pri_chan
(orig_state == next_state) ? "$" : sig_pri_moh_state_str(next_state)); (orig_state == next_state) ? "$" : sig_pri_moh_state_str(next_state));
} }
#if defined(HAVE_PRI_CALL_HOLD)
/*!
* \internal
* \brief Post an AMI hold event.
* \since 10.0
*
* \param chan Channel to post event to
* \param is_held TRUE if the call was placed on hold.
*
* \return Nothing
*/
static void sig_pri_ami_hold_event(struct ast_channel *chan, int is_held)
{
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a PRI channel is put on Hold.</synopsis>
<syntax>
<parameter name="Status">
<enumlist>
<enum name="On"/>
<enum name="Off"/>
</enumlist>
</parameter>
</syntax>
</managerEventInstance>
***/
ast_manager_event(chan, EVENT_FLAG_CALL, "Hold",
"Status: %s\r\n"
"Channel: %s\r\n"
"Uniqueid: %s\r\n",
is_held ? "On" : "Off",
ast_channel_name(chan),
ast_channel_uniqueid(chan));
}
#endif /* defined(HAVE_PRI_CALL_HOLD) */
/*! /*!
* \internal * \internal
* \brief Set callid threadstorage for the pri_dchannel thread when a new call is created * \brief Set callid threadstorage for the pri_dchannel thread when a new call is created
@ -5327,13 +5335,11 @@ static int sig_pri_handle_hold(struct sig_pri_span *pri, pri_event *ev)
goto done_with_owner; goto done_with_owner;
} }
sig_pri_handle_subcmds(pri, chanpos_old, ev->e, ev->hold.subcmds, ev->hold.call); sig_pri_handle_subcmds(pri, chanpos_old, ev->e, ev->hold.subcmds, ev->hold.call);
pri_queue_control(pri, chanpos_old, AST_CONTROL_HOLD); sig_pri_queue_hold(pri, chanpos_old);
chanpos_new = pri_fixup_principle(pri, chanpos_new, ev->hold.call); chanpos_new = pri_fixup_principle(pri, chanpos_new, ev->hold.call);
if (chanpos_new < 0) { if (chanpos_new < 0) {
/* Should never happen. */ /* Should never happen. */
pri_queue_control(pri, chanpos_old, AST_CONTROL_UNHOLD); sig_pri_queue_unhold(pri, chanpos_old);
} else {
sig_pri_ami_hold_event(owner, 1);
} }
done_with_owner:; done_with_owner:;
@ -5521,12 +5527,7 @@ static void sig_pri_handle_retrieve(struct sig_pri_span *pri, pri_event *ev)
sig_pri_lock_private(pri->pvts[chanpos]); sig_pri_lock_private(pri->pvts[chanpos]);
callid = func_pri_dchannel_chanpos_callid(pri, chanpos); callid = func_pri_dchannel_chanpos_callid(pri, chanpos);
sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->retrieve.subcmds, ev->retrieve.call); sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->retrieve.subcmds, ev->retrieve.call);
sig_pri_lock_owner(pri, chanpos); sig_pri_queue_unhold(pri, chanpos);
pri_queue_control(pri, chanpos, AST_CONTROL_UNHOLD);
if (pri->pvts[chanpos]->owner) {
sig_pri_ami_hold_event(pri->pvts[chanpos]->owner, 0);
ast_channel_unlock(pri->pvts[chanpos]->owner);
}
pri_retrieve_ack(pri->pri, ev->retrieve.call, pri_retrieve_ack(pri->pri, ev->retrieve.call,
PVT_TO_CHANNEL(pri->pvts[chanpos])); PVT_TO_CHANNEL(pri->pvts[chanpos]));
sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos], sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos],
@ -7428,12 +7429,12 @@ static void *pri_dchannel(void *vpri)
switch (e->notify.info) { switch (e->notify.info) {
case PRI_NOTIFY_REMOTE_HOLD: case PRI_NOTIFY_REMOTE_HOLD:
if (!pri->discardremoteholdretrieval) { if (!pri->discardremoteholdretrieval) {
pri_queue_control(pri, chanpos, AST_CONTROL_HOLD); sig_pri_queue_hold(pri, chanpos);
} }
break; break;
case PRI_NOTIFY_REMOTE_RETRIEVAL: case PRI_NOTIFY_REMOTE_RETRIEVAL:
if (!pri->discardremoteholdretrieval) { if (!pri->discardremoteholdretrieval) {
pri_queue_control(pri, chanpos, AST_CONTROL_UNHOLD); sig_pri_queue_unhold(pri, chanpos);
} }
break; break;
} }

@ -1202,6 +1202,31 @@ int ast_queue_hangup(struct ast_channel *chan);
*/ */
int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause); int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause);
/*!
* \brief Queue a hold frame
*
* \param chan channel to queue frame onto
* \param musicclass The suggested musicclass for the other end to use
*
* \note The channel does not need to be locked before calling this function.
*
* \retval zero on success
* \retval non-zero on failure
*/
int ast_queue_hold(struct ast_channel *chan, const char *musicclass);
/*!
* \brief Queue an unhold frame
*
* \param chan channel to queue frame onto
*
* \note The channel does not need to be locked before calling this function.
*
* \retval zero on success
* \retval non-zero on failure
*/
int ast_queue_unhold(struct ast_channel *chan);
/*! /*!
* \brief Queue a control frame without payload * \brief Queue a control frame without payload
* *

@ -339,6 +339,22 @@ struct stasis_message_type *ast_channel_dtmf_begin_type(void);
*/ */
struct stasis_message_type *ast_channel_dtmf_end_type(void); struct stasis_message_type *ast_channel_dtmf_end_type(void);
/*!
* \since 12
* \brief Message type for when a channel is placed on hold.
*
* \retval A stasis message type
*/
struct stasis_message_type *ast_channel_hold_type(void);
/*!
* \since 12
* \brief Message type for when a channel is removed from hold.
*
* \retval A stasis message type
*/
struct stasis_message_type *ast_channel_unhold_type(void);
/*! /*!
* \since 12 * \since 12
* \brief Message type for when a channel starts spying on another channel * \brief Message type for when a channel starts spying on another channel

@ -1353,9 +1353,11 @@ static void publish_channel_blob(struct ast_channel *chan,
struct stasis_message_type *type, struct ast_json *blob) struct stasis_message_type *type, struct ast_json *blob)
{ {
RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
if (blob) { if (!blob) {
message = ast_channel_blob_create(chan, type, blob); blob = ast_json_null();
} }
message = ast_channel_blob_create(chan, type, blob);
if (message) { if (message) {
stasis_publish(ast_channel_topic(chan), message); stasis_publish(ast_channel_topic(chan), message);
} }
@ -1405,6 +1407,39 @@ int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause)
return res; return res;
} }
int ast_queue_hold(struct ast_channel *chan, const char *musicclass)
{
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_HOLD };
int res;
if (!ast_strlen_zero(musicclass)) {
f.data.ptr = (void *) musicclass;
f.datalen = strlen(musicclass) + 1;
blob = ast_json_pack("{s: s}",
"musicclass", musicclass);
}
publish_channel_blob(chan, ast_channel_hold_type(), blob);
res = ast_queue_frame(chan, &f);
return res;
}
int ast_queue_unhold(struct ast_channel *chan)
{
RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_UNHOLD };
int res;
publish_channel_blob(chan, ast_channel_unhold_type(), NULL);
res = ast_queue_frame(chan, &f);
return res;
}
/*! \brief Queue a control frame */ /*! \brief Queue a control frame */
int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control) int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control)
{ {
@ -6694,7 +6729,7 @@ static void masquerade_colp_transfer(struct ast_channel *transferee, struct xfer
/* Release any hold on the target. */ /* Release any hold on the target. */
if (colp->target_held) { if (colp->target_held) {
ast_queue_control(transferee, AST_CONTROL_UNHOLD); ast_queue_unhold(transferee);
} }
/* /*

@ -228,6 +228,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</see-also> </see-also>
</managerEventInstance> </managerEventInstance>
</managerEvent> </managerEvent>
<managerEvent language="en_US" name="Hold">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel goes on hold.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<parameter name="MusicClass">
<para>The suggested MusicClass, if provided.</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="Unhold">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel goes off hold.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="ChanSpyStart"> <managerEvent language="en_US" name="ChanSpyStart">
<managerEventInstance class="EVENT_FLAG_CALL"> <managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when one channel begins spying on another channel.</synopsis> <synopsis>Raised when one channel begins spying on another channel.</synopsis>
@ -1180,6 +1199,48 @@ static void channel_dial_cb(void *data, struct stasis_subscription *sub,
} }
static void channel_hold_cb(void *data, struct stasis_subscription *sub,
struct stasis_topic *topic, struct stasis_message *message)
{
struct ast_channel_blob *obj = stasis_message_data(message);
const char *musicclass;
RAII_VAR(struct ast_str *, musicclass_string, NULL, ast_free);
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
if (!(musicclass_string = ast_str_create(32))) {
return;
}
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
if (obj->blob) {
musicclass = ast_json_string_get(ast_json_object_get(obj->blob, "musicclass"));
if (!ast_strlen_zero(musicclass)) {
ast_str_set(&musicclass_string, 0, "MusicClass: %s\r\n", musicclass);
}
}
manager_event(EVENT_FLAG_CALL, "Hold",
"%s"
"%s",
ast_str_buffer(channel_event_string),
ast_str_buffer(musicclass_string));
}
static void channel_unhold_cb(void *data, struct stasis_subscription *sub,
struct stasis_topic *topic, struct stasis_message *message)
{
struct ast_channel_blob *obj = stasis_message_data(message);
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
manager_event(EVENT_FLAG_CALL, "Unhold",
"%s",
ast_str_buffer(channel_event_string));
}
static void manager_channels_shutdown(void) static void manager_channels_shutdown(void)
{ {
stasis_unsubscribe(topic_forwarder); stasis_unsubscribe(topic_forwarder);
@ -1248,6 +1309,16 @@ int manager_channels_init(void)
channel_dial_cb, channel_dial_cb,
NULL); NULL);
ret |= stasis_message_router_add(message_router,
ast_channel_hold_type(),
channel_hold_cb,
NULL);
ret |= stasis_message_router_add(message_router,
ast_channel_unhold_type(),
channel_unhold_cb,
NULL);
ret |= stasis_message_router_add(message_router, ret |= stasis_message_router_add(message_router,
ast_channel_fax_type(), ast_channel_fax_type(),
channel_fax_cb, channel_fax_cb,

@ -48,6 +48,8 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_stop_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_stop_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_fax_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_fax_type);
@ -585,6 +587,8 @@ void ast_stasis_channels_shutdown(void)
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_hangup_request_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_hangup_request_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_dtmf_begin_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_dtmf_begin_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_dtmf_end_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_dtmf_end_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_hold_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_unhold_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_chanspy_start_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_chanspy_start_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_chanspy_stop_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_chanspy_stop_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_fax_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_fax_type);
@ -604,6 +608,8 @@ void ast_stasis_channels_init(void)
STASIS_MESSAGE_TYPE_INIT(ast_channel_hangup_request_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_hangup_request_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_dtmf_begin_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_dtmf_begin_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_dtmf_end_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_dtmf_end_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_hold_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_unhold_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_chanspy_start_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_chanspy_start_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_chanspy_stop_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_chanspy_stop_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_fax_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_fax_type);
@ -612,6 +618,7 @@ void ast_stasis_channels_init(void)
STASIS_MESSAGE_TYPE_INIT(ast_channel_moh_stop_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_moh_stop_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_monitor_start_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_monitor_start_type);
STASIS_MESSAGE_TYPE_INIT(ast_channel_monitor_stop_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_monitor_stop_type);
channel_topic_all = stasis_topic_create("ast_channel_topic_all"); channel_topic_all = stasis_topic_create("ast_channel_topic_all");
channel_topic_all_cached = stasis_caching_topic_create(channel_topic_all, channel_snapshot_get_id); channel_topic_all_cached = stasis_caching_topic_create(channel_topic_all, channel_snapshot_get_id);
} }

@ -698,15 +698,14 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
(!ast_sockaddr_isnull(addrs) || (!ast_sockaddr_isnull(addrs) ||
!pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL))) { !pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL))) {
/* The remote side has taken us off hold */ /* The remote side has taken us off hold */
ast_queue_control(session->channel, AST_CONTROL_UNHOLD); ast_queue_unhold(session->channel);
ast_queue_frame(session->channel, &ast_null_frame); ast_queue_frame(session->channel, &ast_null_frame);
session_media->held = 0; session_media->held = 0;
} else if (ast_sockaddr_isnull(addrs) || } else if (ast_sockaddr_isnull(addrs) ||
ast_sockaddr_is_any(addrs) || ast_sockaddr_is_any(addrs) ||
pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) { pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) {
/* The remote side has put us on hold */ /* The remote side has put us on hold */
ast_queue_control_data(session->channel, AST_CONTROL_HOLD, S_OR(session->endpoint->mohsuggest, NULL), ast_queue_hold(session->channel, session->endpoint->mohsuggest);
!ast_strlen_zero(session->endpoint->mohsuggest) ? strlen(session->endpoint->mohsuggest) + 1 : 0);
ast_rtp_instance_stop(session_media->rtp); ast_rtp_instance_stop(session_media->rtp);
ast_queue_frame(session->channel, &ast_null_frame); ast_queue_frame(session->channel, &ast_null_frame);
session_media->held = 1; session_media->held = 1;

Loading…
Cancel
Save