/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2005-2014, Anthony Minessale II * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is * Anthony Minessale II * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Anthony Minessale II * William King * Emmanuel Schmidbauer * * mod_valet_parking.c -- Valet Parking Module * */ #include #define VALET_EVENT "valet_parking::info" #define VALET_PROTO "park" #define TOKEN_FREQ 5 /* Prototypes */ SWITCH_MODULE_LOAD_FUNCTION(mod_valet_parking_load); /* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) * Defines a switch_loadable_module_function_table_t and a static const char[] modname */ SWITCH_MODULE_DEFINITION(mod_valet_parking, mod_valet_parking_load, NULL, NULL); typedef struct { char ext[256]; char uuid[SWITCH_UUID_FORMATTED_LENGTH + 1]; time_t timeout; int bridged; time_t start_time; } valet_token_t; typedef struct { switch_hash_t *hash; switch_mutex_t *mutex; switch_memory_pool_t *pool; time_t last_timeout_check; char *name; } valet_lot_t; static valet_lot_t globals = { 0 }; static valet_lot_t *valet_find_lot(const char *name, switch_bool_t create) { valet_lot_t *lot; switch_mutex_lock(globals.mutex); lot = switch_core_hash_find(globals.hash, name); if (!lot && create) { switch_zmalloc(lot, sizeof(*lot)); lot->name = strdup(name); switch_mutex_init(&lot->mutex, SWITCH_MUTEX_NESTED, globals.pool); switch_core_hash_init(&lot->hash); switch_core_hash_insert(globals.hash, name, lot); } switch_mutex_unlock(globals.mutex); return lot; } static switch_status_t valet_on_dtmf(switch_core_session_t *session, void *input, switch_input_type_t itype, void *buf, unsigned int buflen) { switch (itype) { case SWITCH_INPUT_TYPE_DTMF: { switch_dtmf_t *dtmf = (switch_dtmf_t *) input; if (dtmf->digit == '#') { return SWITCH_STATUS_BREAK; } } default: break; } return SWITCH_STATUS_SUCCESS; } static void check_timeouts(void) { switch_hash_index_t *hi; const void *var; void *val; time_t now; valet_lot_t *lot; switch_console_callback_match_t *matches = NULL; switch_console_callback_match_node_t *m; switch_hash_index_t *i_hi = NULL; const void *i_var; void *i_val; char *i_ext; valet_token_t *token; now = switch_epoch_time_now(NULL); switch_mutex_lock(globals.mutex); if (now - globals.last_timeout_check < TOKEN_FREQ) { switch_mutex_unlock(globals.mutex); return; } globals.last_timeout_check = now; for (hi = switch_core_hash_first(globals.hash); hi; hi = switch_core_hash_next(&hi)) { switch_core_hash_this(hi, &var, NULL, &val); switch_console_push_match(&matches, (const char *) var); } switch_mutex_unlock(globals.mutex); if (matches) { for (m = matches->head; m; m = m->next) { lot = valet_find_lot(m->val, SWITCH_FALSE); switch_mutex_lock(lot->mutex); top: for (i_hi = switch_core_hash_first_iter( lot->hash, i_hi); i_hi; i_hi = switch_core_hash_next(&i_hi)) { switch_core_hash_this(i_hi, &i_var, NULL, &i_val); i_ext = (char *) i_var; token = (valet_token_t *) i_val; if (token->timeout > 0 && (token->timeout < now || token->timeout == 1)) { switch_core_hash_delete(lot->hash, i_ext); switch_safe_free(token); goto top; } } switch_safe_free(i_hi); switch_mutex_unlock(lot->mutex); } switch_console_free_matches(&matches); } } static int find_longest(valet_lot_t *lot, int min, int max) { switch_hash_index_t *i_hi; const void *i_var; void *i_val; valet_token_t *token; int longest_ext = 0; time_t longest = 0, cur = 0; time_t now = switch_epoch_time_now(NULL); switch_mutex_lock(lot->mutex); for (i_hi = switch_core_hash_first(lot->hash); i_hi; i_hi = switch_core_hash_next(&i_hi)) { int i; switch_core_hash_this(i_hi, &i_var, NULL, &i_val); token = (valet_token_t *) i_val; cur = (now - token->start_time); i = atoi(token->ext); if (cur > longest && i >= min && i <= max) { longest = cur; longest_ext = i; } } switch_mutex_unlock(lot->mutex); return longest_ext; } static valet_token_t *next_id(switch_core_session_t *session, valet_lot_t *lot, int min, int max, int in) { int i, r = 0; char buf[256] = ""; valet_token_t *token; if (!min) { min = 1; } switch_mutex_lock(globals.mutex); if (!in) { int longest = find_longest(lot, min, max); if (longest > 0) { switch_snprintf(buf, sizeof(buf), "%d", longest); switch_mutex_lock(lot->mutex); token = (valet_token_t *) switch_core_hash_find(lot->hash, buf); switch_mutex_unlock(lot->mutex); if (token) { goto end; } } } for (i = min; (i <= max || max == 0); i++) { switch_snprintf(buf, sizeof(buf), "%d", i); switch_mutex_lock(lot->mutex); token = (valet_token_t *) switch_core_hash_find(lot->hash, buf); switch_mutex_unlock(lot->mutex); if ((!in && token && !token->timeout)) { goto end; } if (in && !token) { r = i; break; } } token = NULL; if (r) { switch_snprintf(buf, sizeof(buf), "%d", r); switch_zmalloc(token, sizeof(*token)); switch_set_string(token->uuid, switch_core_session_get_uuid(session)); switch_set_string(token->ext, buf); token->start_time = switch_epoch_time_now(NULL); switch_mutex_lock(lot->mutex); switch_core_hash_insert(lot->hash, buf, token); switch_mutex_unlock(lot->mutex); } end: switch_mutex_unlock(globals.mutex); return token; } static int valet_lot_count(valet_lot_t *lot) { switch_hash_index_t *i_hi; const void *i_var; void *i_val; valet_token_t *token; int count = 0; time_t now; now = switch_epoch_time_now(NULL); switch_mutex_lock(lot->mutex); for (i_hi = switch_core_hash_first(lot->hash); i_hi; i_hi = switch_core_hash_next(&i_hi)) { switch_core_hash_this(i_hi, &i_var, NULL, &i_val); token = (valet_token_t *) i_val; if (token->timeout > 0 && (token->timeout < now || token->timeout == 1)) { continue; } count++; } switch_mutex_unlock(lot->mutex); return count; } static int EC = 0; static void valet_send_presence(const char *lot_name, valet_lot_t *lot, valet_token_t *token, switch_bool_t in) { char *domain_name, *dup_lot_name = NULL, *dup_domain_name = NULL; switch_event_t *event; int count; dup_lot_name = strdup(lot_name); lot_name = dup_lot_name; if ((domain_name = strchr(dup_lot_name, '@'))) { *domain_name++ = '\0'; } if (zstr(domain_name)) { dup_domain_name = switch_core_get_domain(SWITCH_TRUE); domain_name = dup_domain_name; } if (zstr(domain_name)) { domain_name = "cluecon.com"; } count = valet_lot_count(lot); if (count > 0) { if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", lot_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", lot_name, domain_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "force-status", "Active (%d caller%s)", count, count == 1 ? "" : "s"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "active"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_ROUTING"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "confirmed"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound"); switch_event_fire(&event); } } else { if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", lot_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", lot_name, domain_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", "Empty"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "unknown"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_HANGUP"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "terminated"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound"); switch_event_fire(&event); } } if (in) { if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", token->ext); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", token->ext, domain_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", token->bridged == 0 ? "Holding" : "Active"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "active"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", token->ext); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_ROUTING"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "confirmed"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", token->bridged == 0 ? "outbound" : "inbound"); switch_event_fire(&event); } } else { if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", token->ext); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", token->ext, domain_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", "Empty"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "unknown"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", token->ext); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_HANGUP"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "terminated"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound"); switch_event_fire(&event); } } switch_safe_free(dup_domain_name); switch_safe_free(dup_lot_name); } struct read_frame_data { const char *dp; const char *exten; const char *context; long to; }; static switch_status_t read_frame_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data) { struct read_frame_data *rf = (struct read_frame_data *) user_data; if (--rf->to <= 0) { rf->to = -1; return SWITCH_STATUS_FALSE; } return SWITCH_STATUS_SUCCESS; } #define VALET_APP_SYNTAX " |[ask [] [] [] []|auto [in|out] [min] [max]]" SWITCH_STANDARD_APP(valet_parking_function) { char *argv[6], *lbuf; int argc; switch_channel_t *channel = switch_core_session_get_channel(session); switch_event_t *event; char dtmf_buf[128] = ""; int is_auto = 0, play_announce = 1; const char *var; valet_token_t *token = NULL; struct read_frame_data rf = { 0 }; long to_val = 0; check_timeouts(); if ((var = switch_channel_get_variable(channel, "valet_announce_slot"))) { play_announce = switch_true(var); } if (!zstr(data) && (lbuf = switch_core_session_strdup(session, data)) && (argc = switch_separate_string(lbuf, ' ', argv, (sizeof(argv) / sizeof(argv[0])))) >= 2) { char *lot_name = argv[0]; char *ext = argv[1]; valet_lot_t *lot; const char *uuid; const char *music = "silence"; const char *tmp = NULL; switch_status_t status; switch_input_args_t args = { 0 }; char dbuf[10]; char *dest; int in = -1; const char *timeout, *orbit_exten, *orbit_dialplan, *orbit_context; char *timeout_str = "", *orbit_exten_str = "", *orbit_dialplan_str = "", *orbit_context_str = ""; lot = valet_find_lot(lot_name, SWITCH_TRUE); switch_assert(lot); if (!strcasecmp(ext, "auto")) { const char *io = argv[2]; const char *min = argv[3]; const char *max = argv[4]; int min_i, max_i; if (argc < 5) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Usage: %s\n", VALET_APP_SYNTAX); return; } if (io) { if (!strcasecmp(io, "in")) { in = 1; is_auto = 1; } else if (!strcasecmp(io, "out")) { in = 0; } } if (in < 0) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Usage: %s\n", VALET_APP_SYNTAX); return; } min_i = atoi(min); max_i = atoi(max); if (!(token = next_id(session, lot, min_i, max_i, in))) { switch_ivr_phrase_macro(session, in ? "valet_lot_full" : "valet_lot_empty", "", NULL, NULL); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s lot is %s.\n", switch_channel_get_name(channel), in ? "full" : "empty"); return; } switch_snprintf(dtmf_buf, sizeof(dtmf_buf), "%s", token->ext); ext = dtmf_buf; } else if (!strcasecmp(ext, "ask")) { const char *prompt = "ivr/ivr-enter_ext_pound.wav"; int min = 1; int max = 11; int to = 10000; int i; tmp = argv[2] ? argv[2] : switch_channel_get_variable(channel, "valet_ext_min"); if (tmp) { if ((i = atoi(tmp)) > 0) { min = i; } } tmp = argv[3] ? argv[3] : switch_channel_get_variable(channel, "valet_ext_max"); if (tmp) { if ((i = atoi(tmp)) > 0) { max = i; } } tmp = argv[4] ? argv[4] : switch_channel_get_variable(channel, "valet_ext_to"); if (tmp) { if ((i = atoi(tmp)) > 0) { to = i; } } tmp = argv[5] ? argv[5] : switch_channel_get_variable(channel, "valet_ext_prompt"); if (tmp) { prompt = tmp; } do { status = switch_ivr_read(session, min, max, prompt, NULL, dtmf_buf, sizeof(dtmf_buf), to, "#", 0); } while (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_FALSE); if (status == SWITCH_STATUS_SUCCESS) { ext = dtmf_buf; } else { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } } if (!token || !in) { if (!token) { switch_mutex_lock(lot->mutex); token = (valet_token_t *) switch_core_hash_find(lot->hash, ext); switch_mutex_unlock(lot->mutex); } if (token && !token->bridged) { switch_core_session_t *b_session; if (token->timeout) { const char *var = switch_channel_get_variable(channel, "valet_ticket"); if (!zstr(var)) { if (!strcmp(var, token->uuid)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Valet ticket %s accepted.\n", var); token->timeout = 0; switch_channel_set_variable(channel, "valet_ticket", NULL); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid token %s\n", token->uuid); switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return; } } } if (!zstr(token->uuid) && (b_session = switch_core_session_locate(token->uuid))) { if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, VALET_EVENT) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Valet-Lot-Name", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Valet-Extension", ext); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "bridge"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Bridge-To-UUID", switch_core_session_get_uuid(session)); switch_channel_event_set_data(switch_core_session_get_channel(b_session), event); switch_event_fire(&event); switch_core_session_rwunlock(b_session); token->timeout = 0; token->bridged = 1; switch_ivr_uuid_bridge(token->uuid, switch_core_session_get_uuid(session)); return; } } } if (token) { switch_mutex_lock(lot->mutex); switch_core_hash_delete(lot->hash, token->ext); switch_mutex_unlock(lot->mutex); memset(token, 0, sizeof(*token)); } else { switch_zmalloc(token, sizeof(*token)); } switch_set_string(token->uuid, switch_core_session_get_uuid(session)); switch_set_string(token->ext, ext); token->start_time = switch_epoch_time_now(NULL); switch_mutex_lock(lot->mutex); switch_core_hash_insert(lot->hash, ext, token); switch_mutex_unlock(lot->mutex); } if (!(tmp = switch_channel_get_variable(channel, "valet_hold_music"))) { tmp = switch_channel_get_hold_music(channel); } if (tmp) { music = tmp; } if (!strcasecmp(music, "silence")) { music = "silence_stream://-1"; } if ((orbit_exten = switch_channel_get_variable(channel, "valet_parking_orbit_exten"))) { orbit_exten_str = switch_core_session_sprintf(session, "set:valet_parking_orbit_exten=%s,", orbit_exten); } if ((orbit_dialplan = switch_channel_get_variable(channel, "valet_parking_orbit_dialplan"))) { orbit_dialplan_str = switch_core_session_sprintf(session, "set:valet_parking_orbit_dialplan=%s,", orbit_dialplan); } if ((orbit_context = switch_channel_get_variable(channel, "valet_parking_orbit_context"))) { orbit_context_str = switch_core_session_sprintf(session, "set:valet_parking_orbit_context=%s,", orbit_context); } if ((timeout = switch_channel_get_variable(channel, "valet_parking_timeout"))) { timeout_str = switch_core_session_sprintf(session, "set:valet_parking_timeout=%s,", timeout); } dest = switch_core_session_sprintf(session, "%s%s%s%s" "set:valet_ticket=%s,set:valet_hold_music='%s',sleep:1000,valet_park:%s %s", timeout_str, orbit_exten_str, orbit_dialplan_str, orbit_context_str, token->uuid, music, lot_name, ext); switch_channel_set_variable(channel, "inline_destination", dest); if (is_auto) { char tmp[512] = ""; switch_snprintf(tmp, sizeof(tmp), "%s:%s", lot_name, ext); if ((uuid = switch_channel_get_partner_uuid(channel))) { switch_core_session_t *b_session; if ((b_session = switch_core_session_locate(uuid))) { token->timeout = switch_epoch_time_now(NULL) + TOKEN_FREQ; if (play_announce) { switch_ivr_sleep(session, 1500, SWITCH_TRUE, NULL); switch_ivr_phrase_macro(session, "valet_announce_ext", tmp, NULL, NULL); } switch_ivr_session_transfer(b_session, dest, "inline", NULL); switch_core_session_rwunlock(b_session); switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); goto end; } } if (play_announce) { switch_ivr_sleep(session, 1500, SWITCH_TRUE, NULL); switch_ivr_phrase_macro(session, "valet_announce_ext", tmp, NULL, NULL); } } if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, VALET_EVENT) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Valet-Lot-Name", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Valet-Extension", ext); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "hold"); switch_channel_event_set_data(channel, event); switch_event_fire(&event); } switch_channel_set_variable(channel, "valet_lot_extension", ext); valet_send_presence(lot_name, lot, token, SWITCH_TRUE); if ((rf.exten = switch_channel_get_variable(channel, "valet_parking_orbit_exten"))) { to_val = 60; } if ((var = switch_channel_get_variable(channel, "valet_parking_timeout"))) { long tmp = atol(var); if (tmp > 0) { to_val = tmp; } } if (to_val) { switch_codec_implementation_t read_impl; switch_core_session_get_read_impl(session, &read_impl); rf.to = (1000 / (read_impl.microseconds_per_packet / 1000)) * to_val; rf.dp = switch_channel_get_variable(channel, "valet_parking_orbit_dialplan"); rf.context = switch_channel_get_variable(channel, "valet_parking_orbit_context"); } args.input_callback = valet_on_dtmf; args.buf = dbuf; args.buflen = sizeof(dbuf); if (rf.to) { args.read_frame_callback = read_frame_callback; args.user_data = &rf; } while(switch_channel_ready(channel)) { switch_status_t pstatus = switch_ivr_play_file(session, NULL, music, &args); if (rf.to == -1) { if (!zstr(rf.exten)) { switch_ivr_session_transfer(session, rf.exten, rf.dp, rf.context); } break; } if (pstatus == SWITCH_STATUS_BREAK || pstatus == SWITCH_STATUS_TIMEOUT) { break; } } if (token) { token->timeout = 1; valet_send_presence(lot_name, lot, token, SWITCH_FALSE); token = NULL; } if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, VALET_EVENT) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Valet-Lot-Name", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Valet-Extension", ext); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "exit"); switch_channel_event_set_data(channel, event); switch_event_fire(&event); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Usage: %s\n", VALET_APP_SYNTAX); } end: if (token) { token->timeout = 1; } } SWITCH_STANDARD_API(valet_info_function) { switch_hash_index_t *hi; const void *var; void *val; char *name; valet_lot_t *lot; stream->write_function(stream, "\n"); switch_mutex_lock(globals.mutex); for (hi = switch_core_hash_first(globals.hash); hi; hi = switch_core_hash_next(&hi)) { switch_hash_index_t *i_hi; const void *i_var; void *i_val; char *i_ext; switch_core_hash_this(hi, &var, NULL, &val); name = (char *) var; lot = (valet_lot_t *) val; if (!zstr(cmd) && strcasecmp(cmd, name)) continue; stream->write_function(stream, " \n", name); switch_mutex_lock(lot->mutex); for (i_hi = switch_core_hash_first(lot->hash); i_hi; i_hi = switch_core_hash_next(&i_hi)) { valet_token_t *token; switch_core_hash_this(i_hi, &i_var, NULL, &i_val); i_ext = (char *) i_var; token = (valet_token_t *) i_val; if (!token->timeout) { stream->write_function(stream, " %s\n", token->uuid, i_ext); } } switch_mutex_unlock(lot->mutex); stream->write_function(stream, " \n"); } stream->write_function(stream, "\n"); switch_mutex_unlock(globals.mutex); return SWITCH_STATUS_SUCCESS; } static void pres_event_handler(switch_event_t *event) { char *to = switch_event_get_header(event, "to"); char *dup_to = NULL, *lot_name, *dup_lot_name = NULL, *domain_name; valet_lot_t *lot; int found = 0; const char *call_id; if (!to || strncasecmp(to, "park+", 5) || !strchr(to, '@')) { return; } if (!(dup_to = strdup(to))) { return; } lot_name = dup_to + 5; if ((domain_name = strchr(lot_name, '@'))) { *domain_name++ = '\0'; } call_id = switch_event_get_header(event, "sub-call-id"); dup_lot_name = switch_mprintf("%q@%q", lot_name, domain_name); if ((lot = valet_find_lot(lot_name, SWITCH_FALSE)) || (dup_lot_name && (lot = valet_find_lot(dup_lot_name, SWITCH_FALSE)))) { int count = valet_lot_count(lot); if (count) { if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", lot_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", lot_name, domain_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "force-status", "Active (%d caller%s)", count, count == 1 ? "" : "s"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "active"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_ROUTING"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "confirmed"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound"); if (call_id) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-id", call_id); } switch_event_fire(&event); } found++; } else { if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", lot_name, domain_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", "Empty"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "unknown"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_HANGUP"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "terminated"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound"); if (call_id) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-id", call_id); } switch_event_fire(&event); } } } else { switch_console_callback_match_t *matches = NULL; switch_console_callback_match_node_t *m; switch_hash_index_t *hi; const void *var; void *val; const char *nvar; switch_mutex_lock(globals.mutex); for (hi = switch_core_hash_first(globals.hash); hi; hi = switch_core_hash_next(&hi)) { switch_core_hash_this(hi, &var, NULL, &val); nvar = (const char *) var; if (!strchr(nvar, '@') || switch_stristr(domain_name, nvar)) { switch_console_push_match(&matches, nvar); } } switch_mutex_unlock(globals.mutex); if (matches) { valet_token_t *token; for (m = matches->head; !found && m; m = m->next) { lot = valet_find_lot(m->val, SWITCH_FALSE); switch_mutex_lock(lot->mutex); if ((token = (valet_token_t *) switch_core_hash_find(lot->hash, lot_name)) && !token->timeout) { found++; if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", lot_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", lot_name, domain_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", token->bridged == 0 ? "Holding" : "Active"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_ROUTING"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "confirmed"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", token->bridged == 0 ? "outbound" : "inbound"); if (call_id) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-id", call_id); } switch_event_fire(&event); } } switch_mutex_unlock(lot->mutex); } switch_console_free_matches(&matches); } } if (!found && switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VALET_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", lot_name); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", lot_name, domain_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", "Empty"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "unknown"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog"); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", lot_name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_HANGUP"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "terminated"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "presence-call-direction", "inbound"); if (call_id) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-id", call_id); } switch_event_fire(&event); } switch_safe_free(dup_to); switch_safe_free(dup_lot_name); } /* Macro expands to: switch_status_t mod_valet_parking_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */ SWITCH_MODULE_LOAD_FUNCTION(mod_valet_parking_load) { switch_application_interface_t *app_interface; switch_api_interface_t *api_interface; /* create/register custom event message type */ if (switch_event_reserve_subclass(VALET_EVENT) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", VALET_EVENT); return SWITCH_STATUS_TERM; } switch_event_bind(modname, SWITCH_EVENT_PRESENCE_PROBE, SWITCH_EVENT_SUBCLASS_ANY, pres_event_handler, NULL); memset(&globals, 0, sizeof(globals)); globals.pool = pool; switch_core_hash_init(&globals.hash); switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); /* connect my internal structure to the blank pointer passed to me */ *module_interface = switch_loadable_module_create_module_interface(pool, modname); SWITCH_ADD_APP(app_interface, "valet_park", "valet_park", "valet_park", valet_parking_function, VALET_APP_SYNTAX, SAF_NONE); SWITCH_ADD_API(api_interface, "valet_info", "Valet Parking Info", valet_info_function, "[]"); return SWITCH_STATUS_NOUNLOAD; } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet */