diff --git a/build/modules.conf.in b/build/modules.conf.in index 5613d67130..a6c32f3960 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -10,6 +10,7 @@ applications/mod_fifo #applications/mod_fax #applications/mod_curl applications/mod_voicemail +#applications/mod_directory #applications/mod_lcr applications/mod_limit applications/mod_expr diff --git a/conf/autoload_configs/directory.conf.xml b/conf/autoload_configs/directory.conf.xml new file mode 100644 index 0000000000..7d99dc1e71 --- /dev/null +++ b/conf/autoload_configs/directory.conf.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/conf/autoload_configs/modules.conf.xml b/conf/autoload_configs/modules.conf.xml index 98ca27344b..50208b78da 100644 --- a/conf/autoload_configs/modules.conf.xml +++ b/conf/autoload_configs/modules.conf.xml @@ -44,6 +44,7 @@ + diff --git a/conf/lang/en/dir/sounds.xml b/conf/lang/en/dir/sounds.xml new file mode 100644 index 0000000000..02d6b3671f --- /dev/null +++ b/conf/lang/en/dir/sounds.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/lang/en/dir/tts.xml b/conf/lang/en/dir/tts.xml new file mode 100644 index 0000000000..67ae5ab265 --- /dev/null +++ b/conf/lang/en/dir/tts.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/lang/en/en.xml b/conf/lang/en/en.xml index 92397ba390..1b3215f96c 100644 --- a/conf/lang/en/en.xml +++ b/conf/lang/en/en.xml @@ -3,5 +3,6 @@ + diff --git a/conf/lang/fr/dir/sounds.xml b/conf/lang/fr/dir/sounds.xml new file mode 100644 index 0000000000..02d6b3671f --- /dev/null +++ b/conf/lang/fr/dir/sounds.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/lang/fr/dir/tts.xml b/conf/lang/fr/dir/tts.xml new file mode 100644 index 0000000000..84b9859c4a --- /dev/null +++ b/conf/lang/fr/dir/tts.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/lang/fr/fr.xml b/conf/lang/fr/fr.xml index 40c038f5b1..12bec06f20 100644 --- a/conf/lang/fr/fr.xml +++ b/conf/lang/fr/fr.xml @@ -2,6 +2,7 @@ - + + diff --git a/docs/phrase/phrase_en.xml b/docs/phrase/phrase_en.xml index e516ce1053..ae35c67002 100644 --- a/docs/phrase/phrase_en.xml +++ b/docs/phrase/phrase_en.xml @@ -219,6 +219,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/docs/phrase/phrase_fr.xml b/docs/phrase/phrase_fr.xml index 8f2212309b..ef3ee7bb6d 100644 --- a/docs/phrase/phrase_fr.xml +++ b/docs/phrase/phrase_fr.xml @@ -218,7 +218,26 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/freeswitch.spec b/freeswitch.spec index f970d1fde8..379856b4be 100644 --- a/freeswitch.spec +++ b/freeswitch.spec @@ -314,7 +314,7 @@ export QA_RPATHS=$[ 0x0001|0x0002 ] PASSTHRU_CODEC_MODULES="codecs/mod_g729 codecs/mod_g723_1 codecs/mod_amr codecs/mod_amrwb" SPIDERMONKEY_MODULES="languages/mod_spidermonkey languages/mod_spidermonkey_curl languages/mod_spidermonkey_core_db languages/mod_spidermonkey_odbc languages/mod_spidermonkey_socket languages/mod_spidermonkey_teletone" -APPLICATIONS_MODULES="applications/mod_commands applications/mod_conference applications/mod_dptools applications/mod_enum applications/mod_esf applications/mod_expr applications/mod_fifo applications/mod_limit applications/mod_rss applications/mod_voicemail applications/mod_fsv applications/mod_lcr applications/mod_easyroute applications/mod_stress applications/mod_vmd applications/mod_limit applications/mod_soundtouch applications/mod_fax" +APPLICATIONS_MODULES="applications/mod_commands applications/mod_conference applications/mod_dptools applications/mod_enum applications/mod_esf applications/mod_expr applications/mod_fifo applications/mod_limit applications/mod_rss applications/mod_voicemail applications/mod_directory applications/mod_fsv applications/mod_lcr applications/mod_easyroute applications/mod_stress applications/mod_vmd applications/mod_limit applications/mod_soundtouch applications/mod_fax" CODECS_MODULES="codecs/mod_ilbc codecs/mod_h26x codecs/mod_voipcodecs codecs/mod_speex codecs/mod_celt codecs/mod_siren" DIALPLANS_MODULES="dialplans/mod_dialplan_asterisk dialplans/mod_dialplan_directory dialplans/mod_dialplan_xml" DIRECTORIES_MODULES="" @@ -541,6 +541,7 @@ userdel freeswitch %{prefix}/mod/mod_limit.so* %{prefix}/mod/mod_rss.so* %{prefix}/mod/mod_voicemail.so* +%{prefix}/mod/mod_directory.so* %{prefix}/mod/mod_pocketsphinx.so* %{prefix}/mod/mod_flite.so* %{prefix}/mod/mod_ilbc.so* diff --git a/src/mod/applications/mod_directory/Makefile b/src/mod/applications/mod_directory/Makefile new file mode 100644 index 0000000000..53a1f3700f --- /dev/null +++ b/src/mod/applications/mod_directory/Makefile @@ -0,0 +1,2 @@ +BASE=../../../.. +include $(BASE)/build/modmake.rules \ No newline at end of file diff --git a/src/mod/applications/mod_directory/mod_directory.2008.vcproj b/src/mod/applications/mod_directory/mod_directory.2008.vcproj new file mode 100644 index 0000000000..a5e2d4b248 --- /dev/null +++ b/src/mod/applications/mod_directory/mod_directory.2008.vcproj @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mod/applications/mod_directory/mod_directory.c b/src/mod/applications/mod_directory/mod_directory.c new file mode 100644 index 0000000000..6c8bef209a --- /dev/null +++ b/src/mod/applications/mod_directory/mod_directory.c @@ -0,0 +1,990 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2009, 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): + * + * Marc Olivier Chouinard + * + * + * mod_directory.c -- Search by Name Directory IVR + * + */ +#include + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_directory_shutdown); +SWITCH_MODULE_LOAD_FUNCTION(mod_directory_load); + +SWITCH_MODULE_DEFINITION(mod_directory, mod_directory_load, mod_directory_shutdown, NULL); + +static const char *global_cf = "directory.conf"; + +static char dir_sql[] = +"CREATE TABLE directory_search (\n" +" hostname VARCHAR(255),\n" +" uuid VARCHAR(255),\n" +" extension VARCHAR(255),\n" +" full_name VARCHAR(255),\n" +" full_name_digit VARCHAR(255),\n" +" first_name VARCHAR(255),\n" +" first_name_digit VARCHAR(255),\n" +" last_name VARCHAR(255),\n" +" last_name_digit VARCHAR(255),\n" +" name_visible INTEGER,\n" +" exten_visible INTEGER\n" +");\n"; + +#define DIR_RESULT_ITEM "directory_result_item" +#define DIR_RESULT_SAY_NAME "directory_result_say_name" +#define DIR_RESULT_AT "directory_result_at" +#define DIR_RESULT_MENU "directory_result_menu" +#define DIR_INTRO "directory_intro" +#define DIR_MIN_SEARCH_DIGITS "directory_min_search_digits" +#define DIR_RESULT_COUNT "directory_result_count" +#define DIR_RESULT_COUNT_TOO_LARGE "directory_result_count_too_large" +#define DIR_RESULT_LAST "directory_result_last" + +static switch_xml_config_string_options_t config_dtmf = { NULL, 2, "[0-9#\\*]" }; +static switch_xml_config_int_options_t config_int_digit_timeout = { SWITCH_TRUE, 0, SWITCH_TRUE, 30000 }; +static switch_xml_config_int_options_t config_int_ht_0 = { SWITCH_TRUE, 0 }; + +static struct { + switch_hash_t *profile_hash; + char hostname[256]; + int integer; + int debug; + char *dbname; + switch_mutex_t *mutex; + switch_memory_pool_t *pool; +} globals; + +#define DIR_PROFILE_CONFIGITEM_COUNT 100 + +struct dir_profile { + char *name; + + char next_key[2]; + char prev_key[2]; + char select_name_key[2]; + char new_search_key[2]; + + char terminator_key[2]; + char switch_order_key[2]; + + char *search_order; + + uint32_t min_search_digits; + uint32_t max_menu_attempt; + uint32_t digit_timeout; + uint32_t max_result; + switch_mutex_t *mutex; + + switch_thread_rwlock_t *rwlock; + switch_memory_pool_t *pool; + + switch_xml_config_item_t config[DIR_PROFILE_CONFIGITEM_COUNT]; + switch_xml_config_string_options_t config_str_pool; + uint32_t flags; +}; + +typedef struct dir_profile dir_profile_t; + +typedef enum { + PFLAG_DESTROY = 1 << 0 +} dir_flags_t; + +static int digit_matching_keypad(char letter) { + int result = -1; + switch(toupper(letter)) { + case 'A': + case 'B': + case 'C': + result = 2; + break; + case 'D': + case 'E': + case 'F': + result = 3; + break; + case 'G': + case 'H': + case 'I': + result = 4; + break; + case 'J': + case 'K': + case 'L': + result = 5; + break; + case 'M': + case 'N': + case 'O': + result = 6; + break; + case 'P': + case 'Q': + case 'R': + case 'S': + result = 7; + break; + case 'T': + case 'U': + case 'V': + result = 8; + break; + case 'W': + case 'X': + case 'Y': + case 'Z': + result = 9; + break; + } + + + return result; + +} + +char *string_to_keypad_digit(const char *in) +{ + const char *s = NULL; + char *dst = NULL; + char *d = NULL; + + if (in) { + s = in; + dst = strdup(in); + d = dst; + + while (*s) { + char c; + if ((c = digit_matching_keypad(*s++)) > 0) { + *d++ = c + 48; + } + } + if (*d) { + *d = '\0'; + } + } + return dst; +} + +static switch_status_t directory_execute_sql(char *sql, switch_mutex_t *mutex) +{ + switch_core_db_t *db; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + if (mutex) { + switch_mutex_lock(mutex); + } + + if (!(db = switch_core_db_open_file(globals.dbname))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB %s\n", globals.dbname); + status = SWITCH_STATUS_FALSE; + goto end; + } + status = switch_core_db_persistant_execute(db, sql, 1); + switch_core_db_close(db); + +end: + if (mutex) { + switch_mutex_unlock(mutex); + } + + return status; +} + +typedef enum { + ENTRY_MOVE_NEXT, + ENTRY_MOVE_PREV +} entry_move_t; + +struct search_params { + char digits[255]; + char transfer_to[255]; + char domain[255]; + char profile[255]; + int search_by_last_name; + int timeout; + int try_again; +}; +typedef struct search_params search_params_t; + +struct listing_callback { + char extension[255]; + char fullname[255]; + char first_name[255]; + char last_name[255]; + char transfer_to[255]; + int name_visible; + int exten_visible; + int new_search; + int index; + int want; + entry_move_t move; + search_params_t *params; +}; +typedef struct listing_callback listing_callback_t; + +static int listing_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + listing_callback_t *cbt = (listing_callback_t *) pArg; + if (cbt->index++ != cbt->want) { + return 0; + } + switch_copy_string(cbt->extension, argv[0], 255); + switch_copy_string(cbt->fullname, argv[1], 255); + switch_copy_string(cbt->last_name, argv[2], 255); + switch_copy_string(cbt->first_name, argv[3], 255); + cbt->name_visible = atoi(argv[4]); + cbt->exten_visible = atoi(argv[5]); + return -1; +} + + +struct callback { + char *buf; + size_t len; + int matches; +}; +typedef struct callback callback_t; + +static int sql2str_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + callback_t *cbt = (callback_t *) pArg; + + switch_copy_string(cbt->buf, argv[0], cbt->len); + cbt->matches++; + return 0; +} + +static switch_bool_t directory_execute_sql_callback(switch_mutex_t *mutex, char *sql, switch_core_db_callback_func_t callback, void *pdata) +{ + switch_bool_t ret = SWITCH_FALSE; + switch_core_db_t *db; + char *errmsg = NULL; + + if (mutex) { + switch_mutex_lock(mutex); + } + + if (!(db = switch_core_db_open_file(globals.dbname))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB %s\n", globals.dbname); + goto end; + } + + switch_core_db_exec(db, sql, callback, pdata, &errmsg); + + if (errmsg) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SQL ERR: [%s] %s\n", sql, errmsg); + free(errmsg); + } + + if (db) { + switch_core_db_close(db); + } + +end: + if (mutex) { + switch_mutex_unlock(mutex); + } + + return ret; +} + +#define DIR_DESC "directory" +#define DIR_USAGE " " + +static void free_profile(dir_profile_t *profile) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying Profile %s\n", profile->name); + switch_core_destroy_memory_pool(&profile->pool); +} + +static void profile_rwunlock(dir_profile_t *profile) +{ + switch_thread_rwlock_unlock(profile->rwlock); + if (switch_test_flag(profile, PFLAG_DESTROY)) { + if (switch_thread_rwlock_tryrdlock(profile->rwlock) == SWITCH_STATUS_SUCCESS) { + free_profile(profile); + } + } +} + +dir_profile_t *profile_set_config(dir_profile_t *profile) +{ + int i = 0; + + profile->config_str_pool.pool = profile->pool; + + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "next-key", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->next_key, "6", &config_dtmf, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "prev-key", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->prev_key, "4", &config_dtmf, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "terminator-key", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->terminator_key, "#", &config_dtmf, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "switch-order-key", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->switch_order_key, "*", &config_dtmf, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "select-name-key", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->select_name_key, "1", &config_dtmf, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "new-search-key", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->new_search_key, "3", &config_dtmf, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "search-order", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, + &profile->search_order, "last_name", &profile->config_str_pool, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "digit-timeout", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, + &profile->digit_timeout, 3000, &config_int_digit_timeout, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "min-search-digits", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, + &profile->min_search_digits, 3, &config_int_ht_0, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "max-menu-attempts", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, + &profile->max_menu_attempt, 3, &config_int_ht_0, NULL, NULL); + SWITCH_CONFIG_SET_ITEM(profile->config[i++], "max-result", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, + &profile->max_result, 5, &config_int_ht_0, NULL, NULL); + + return profile; + +} + +static dir_profile_t * load_profile(const char *profile_name) +{ + dir_profile_t *profile = NULL; + switch_xml_t x_profiles, x_profile, cfg, xml = NULL; + switch_event_t *event = NULL; + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + return profile; + } + if (!(x_profiles = switch_xml_child(cfg, "profiles"))) { + goto end; + } + + if ((x_profile = switch_xml_find_child(x_profiles, "profile", "name", profile_name))) { + switch_memory_pool_t *pool; + int count; + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n"); + goto end; + } + + if (!(profile = switch_core_alloc(pool, sizeof(dir_profile_t)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n"); + switch_core_destroy_memory_pool(&pool); + goto end; + } + + profile->pool = pool; + profile_set_config(profile); + + /* Add the params to the event structure */ + count = switch_event_import_xml(switch_xml_child(x_profile, "param"), "name", "value", &event); + + if (switch_xml_config_parse_event(event, count, SWITCH_FALSE, profile->config) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to process configuration\n"); + switch_core_destroy_memory_pool(&pool); + goto end; + } + + switch_thread_rwlock_create(&profile->rwlock, pool); + profile->name = switch_core_strdup(pool, profile_name); + + switch_mutex_init(&profile->mutex, SWITCH_MUTEX_NESTED, profile->pool); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Added Profile %s\n", profile->name); + switch_core_hash_insert(globals.profile_hash, profile->name, profile); + } + +end: + switch_xml_free(xml); + + return profile; +} + +static switch_status_t load_config(switch_bool_t reload) +{ + switch_xml_t cfg, xml = NULL, settings, param, x_profiles, x_profile; + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + return SWITCH_STATUS_TERM; + } + + switch_mutex_lock(globals.mutex); + if ((settings = switch_xml_child(cfg, "settings"))) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + if (!strcasecmp(var, "debug")) { + globals.debug = atoi(val); + } + } + } + + if ((x_profiles = switch_xml_child(cfg, "profiles"))) { + for (x_profile = switch_xml_child(x_profiles, "profile"); x_profile; x_profile = x_profile->next) { + load_profile(switch_xml_attr_soft(x_profile, "name")); + } + } + switch_mutex_unlock(globals.mutex); + + switch_xml_free(xml); + return SWITCH_STATUS_SUCCESS; +} + +static dir_profile_t * get_profile(const char *profile_name) +{ + dir_profile_t *profile = NULL; + + switch_mutex_lock(globals.mutex); + if (!(profile = switch_core_hash_find(globals.profile_hash, profile_name))) { + profile = load_profile(profile_name); + } + if (profile) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[%s] rwlock\n", profile->name); + + switch_thread_rwlock_rdlock(profile->rwlock); + } + switch_mutex_unlock(globals.mutex); + + return profile; +} + +static switch_status_t populate_database(switch_core_session_t *session, dir_profile_t * profile, const char *domain_name) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + + char *sql = NULL; + char *sqlvalues = NULL; + char *sqltmp = NULL; + + switch_xml_t xml_root = NULL, x_domain; + switch_xml_t ut; + + switch_event_t *xml_params = NULL; + switch_xml_t group = NULL, groups = NULL, users = NULL, x_params = NULL, x_param = NULL, x_vars = NULL, x_var = NULL; + switch_event_create(&xml_params, SWITCH_EVENT_REQUEST_PARAMS); + switch_assert(xml_params); + + if (switch_xml_locate_domain(domain_name, xml_params, &xml_root, &x_domain) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot locate domain %s\n", domain_name); + status = SWITCH_STATUS_FALSE; + goto end; + } + + if ((groups = switch_xml_child(x_domain, "groups"))) { + for (group = switch_xml_child(groups, "group"); group; group = group->next) { + if ((users = switch_xml_child(group, "users"))) { + for (ut = switch_xml_child(users, "user"); ut; ut = ut->next) { + int name_visible = 1; + int exten_visible = 1; + const char *type = switch_xml_attr_soft(ut, "type"); + const char *id = switch_xml_attr_soft(ut, "id"); + char *fullName = NULL; + char *caller_name = NULL; + char *caller_name_override = NULL; + char *firstName = NULL; + char *lastName = NULL; + char *fullNameDigit = NULL; + char *firstNameDigit = NULL; + char *lastNameDigit = NULL; + + if (!strcasecmp(type, "pointer")) { + continue; + } + /* Check all the user params */ + if ((x_params = switch_xml_child(ut, "params"))) { + for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) { + const char *var = switch_xml_attr_soft(x_param, "name"); + const char *val = switch_xml_attr_soft(x_param, "value"); + if (!strcasecmp(var, "directory-visible")) { + name_visible = switch_true(val); + } + if (!strcasecmp(var, "directory-exten-visible")) { + exten_visible = switch_true(val); + } + + } + } + /* Check all the user variables */ + if ((x_vars = switch_xml_child(ut, "variables"))) { + for (x_var = switch_xml_child(x_vars, "variable"); x_var; x_var = x_var->next) { + const char *var = switch_xml_attr_soft(x_var, "name"); + const char *val = switch_xml_attr_soft(x_var, "value"); + if (!strcasecmp(var, "effective_caller_id_name")) { + caller_name = switch_core_session_strdup(session, val); + } + if (!strcasecmp(var, "directory_full_name")) { + caller_name_override = switch_core_session_strdup(session, val); + } + } + } + if (caller_name_override) { + fullName = caller_name_override; + } else { + fullName = caller_name; + } + if (switch_strlen_zero(fullName)) { + continue; + } + firstName = switch_core_session_strdup(session, fullName); + + if ((lastName = strrchr(firstName, ' '))) { + *lastName++ = '\0'; + } else { + lastName = switch_core_session_strdup(session, firstName); + } + + /* switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "FullName %s firstName [%s] lastName [%s]\n", fullName, firstName, lastName); */ + + /* Generate Digits key mapping */ + fullNameDigit = string_to_keypad_digit(fullName); + lastNameDigit = string_to_keypad_digit(lastName); + firstNameDigit = string_to_keypad_digit(firstName); + + /* add user into DB */ + sql = switch_mprintf("insert into directory_search values('%q','%q','%q','%q','%q','%q','%q','%q','%q','%d','%d')", + globals.hostname, switch_core_session_get_uuid(session), id, fullName, fullNameDigit, firstName, firstNameDigit, lastName, lastNameDigit, name_visible, exten_visible); + + if (sqlvalues) { + sqltmp = sqlvalues; + sqlvalues = switch_mprintf("%s;%s", sqlvalues, sql); + switch_safe_free(sqltmp); + } else { + sqlvalues = sql; + sql = NULL; + } + switch_safe_free(sql); + switch_safe_free(fullNameDigit); + switch_safe_free(lastNameDigit); + switch_safe_free(firstNameDigit); + } + } + } + } + sql = switch_mprintf("BEGIN;%s;COMMIT;",sqlvalues); + directory_execute_sql(sql, profile->mutex); + +end: + switch_safe_free(sql); + switch_safe_free(sqlvalues); + switch_event_destroy(&xml_params); + switch_xml_free(xml_root); + + return status; +} + +struct cb_result { + char digits[255]; + char digit; + dir_profile_t *profile; +}; + +typedef struct cb_result cbr_t; + +static switch_status_t 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; + cbr_t *cbr = (cbr_t *) buf; + cbr->digit = dtmf->digit; + if (dtmf->digit == *cbr->profile->terminator_key || dtmf->digit == *cbr->profile->switch_order_key) { + return SWITCH_STATUS_BREAK; + } + + if (strlen(cbr->digits) < sizeof(cbr->digits) - 2) { + int at = strlen(cbr->digits); + cbr->digits[at++] = dtmf->digit; + cbr->digits[at] = '\0'; + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "DTMF buffer is full\n"); + return SWITCH_STATUS_BREAK; + } + } + break; + default: + break; + } + + return SWITCH_STATUS_BREAK; +} + +static switch_status_t listen_entry(switch_core_session_t *session, dir_profile_t *profile, listing_callback_t *cbt) +{ + char buf[2] = ""; + char macro[256] = ""; + char recorded_name[256] = ""; + + /* Try to use the recorded name from voicemail if it exist */ + if (switch_loadable_module_exists("mod_voicemail") == SWITCH_STATUS_SUCCESS) { + char *cmd = NULL; + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + + cmd = switch_core_session_sprintf(session, "%s/%s@%s|name_path", cbt->params->profile, cbt->extension, cbt->params->domain); + switch_api_execute("vm_prefs", cmd, session, &stream); + if (strncmp("-ERR", stream.data, 4)) { + switch_copy_string(recorded_name, (char *) stream.data, sizeof(recorded_name)); + } + switch_safe_free(stream.data); + } + + if (switch_strlen_zero(buf)) { + switch_snprintf(macro, sizeof(macro), "phrase:%s:%d", DIR_RESULT_ITEM, cbt->want+1); + switch_ivr_read(session, 0, 1, macro, NULL, buf, sizeof(buf), 1, profile->terminator_key); + } + + if (!switch_strlen_zero(recorded_name) && switch_strlen_zero(buf)) { + switch_ivr_read(session, 0, 1, recorded_name, NULL, buf, sizeof(buf), 1, profile->terminator_key); + + } + if (switch_strlen_zero(recorded_name) && switch_strlen_zero(buf)) { + switch_snprintf(macro, sizeof(macro), "phrase:%s:%s", DIR_RESULT_SAY_NAME, cbt->fullname); + switch_ivr_read(session, 0, 1, macro, NULL, buf, sizeof(buf), 1, profile->terminator_key); + } + if (cbt->exten_visible && switch_strlen_zero(buf)) { + switch_snprintf(macro, sizeof(macro), "phrase:%s:%s", DIR_RESULT_AT, cbt->extension); + switch_ivr_read(session, 0, 1, macro, NULL, buf, sizeof(buf), 1, profile->terminator_key); + } + if (switch_strlen_zero(buf)) { + switch_snprintf(macro, sizeof(macro), "phrase:%s:%c,%c,%c,%c", DIR_RESULT_MENU, *profile->select_name_key, *profile->next_key, *profile->prev_key, *profile->new_search_key); + switch_ivr_read(session, 0, 1, macro, NULL, buf, sizeof(buf), profile->digit_timeout, profile->terminator_key); + } + + if (!switch_strlen_zero(buf)) { + if (buf[0] == *profile->select_name_key) { + switch_copy_string(cbt->transfer_to, cbt->extension, 255); + } + if (buf[0] == *profile->new_search_key) { + cbt->new_search = 1; + } + if (buf[0] == *profile->prev_key) { + cbt->move = ENTRY_MOVE_PREV; + } + } else { + return SWITCH_STATUS_TIMEOUT; + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t gather_name_digit(switch_core_session_t *session, dir_profile_t *profile, search_params_t *params) { + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_status_t status = SWITCH_STATUS_SUCCESS; + cbr_t cbr; + int loop = 1; + + switch_input_args_t args = { 0 }; + args.input_callback = on_dtmf; + args.buf = &cbr; + + while (switch_channel_ready(channel) && loop) { + char macro[255]; + loop = 0; + memset(&cbr, 0, sizeof(cbr)); + cbr.profile = profile; + params->timeout = 0; + + /* Gather the user Name */ + + switch_snprintf(macro, sizeof(macro), "%s:%c", (params->search_by_last_name?"last_name":"first_name"), *profile->switch_order_key); + switch_ivr_phrase_macro(session, DIR_INTRO, macro, NULL, &args); + + while (switch_channel_ready(channel)) { + if (cbr.digit == *profile->terminator_key) { + status = SWITCH_STATUS_BREAK; + break; + } + if (cbr.digit == *profile->switch_order_key) { + if (params->search_by_last_name) { + params->search_by_last_name = 0; + } else { + params->search_by_last_name = 1; + } + loop = 1; + break; + } + + if (switch_ivr_collect_digits_callback(session, &args, profile->digit_timeout, 0) == SWITCH_STATUS_TIMEOUT) { + params->timeout = 1; + break; + } + + } + } + switch_copy_string(params->digits, cbr.digits, 255); + + return status; +} + +switch_status_t navigate_entrys(switch_core_session_t *session, dir_profile_t *profile, search_params_t *params) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + char *sql = NULL; + char entry_count[80] = ""; + callback_t cbt = { 0 }; + int result_count; + char macro[256] = ""; + listing_callback_t listing_cbt; + int cur_entry = 0; + cbt.buf = entry_count; + cbt.len = sizeof(entry_count); + + sql = switch_mprintf("select count(*) from directory_search where hostname = '%q' and uuid = '%q' and name_visible = 1 and %s like '%q%%'", globals.hostname, switch_core_session_get_uuid(session), (params->search_by_last_name?"last_name_digit":"first_name_digit"), params->digits); + + directory_execute_sql_callback(profile->mutex, sql, sql2str_callback, &cbt); + switch_safe_free(sql); + + result_count = atoi(entry_count); + + if (result_count == 0) { + switch_snprintf(macro, sizeof(macro), "%d", result_count); + switch_ivr_phrase_macro(session, DIR_RESULT_COUNT, macro, NULL, NULL); + params->try_again = 1; + return SWITCH_STATUS_BREAK; + } else if (profile->max_result != 0 && result_count > profile->max_result) { + switch_ivr_phrase_macro(session, DIR_RESULT_COUNT_TOO_LARGE, NULL, NULL, NULL); + params->try_again = 1; + return SWITCH_STATUS_BREAK; + } else { + switch_snprintf(macro, sizeof(macro), "%d", result_count); + switch_ivr_phrase_macro(session, DIR_RESULT_COUNT, macro, NULL, NULL); + } + + memset(&listing_cbt, 0, sizeof(listing_cbt)); + listing_cbt.params = params; + + sql = switch_mprintf("select extension, full_name, last_name, first_name, name_visible, exten_visible from directory_search where hostname = '%q' and uuid = '%q' and name_visible = 1 and %s like '%q%%' order by last_name, first_name", globals.hostname, switch_core_session_get_uuid(session), (params->search_by_last_name?"last_name_digit":"first_name_digit"), params->digits); + + for (cur_entry = 0; cur_entry < result_count; cur_entry++) { + listing_cbt.index = 0; + listing_cbt.want = cur_entry; + listing_cbt.move = ENTRY_MOVE_NEXT; + directory_execute_sql_callback(profile->mutex, sql, listing_callback, &listing_cbt); + status = listen_entry(session, profile, &listing_cbt); + if (!switch_strlen_zero(listing_cbt.transfer_to)) { + switch_copy_string(params->transfer_to, listing_cbt.transfer_to, 255); + break; + } + if (listing_cbt.new_search) { + params->try_again = 1; + goto end; + + } + if (listing_cbt.move == ENTRY_MOVE_NEXT) { + if (cur_entry == result_count - 1) { + switch_snprintf(macro, sizeof(macro), "%d", result_count); + switch_ivr_phrase_macro(session, DIR_RESULT_LAST, macro, NULL, NULL); + cur_entry -= 1; + } + } + if (listing_cbt.move == ENTRY_MOVE_PREV) { + if (cur_entry <= 0) { + cur_entry = -1; + } else { + cur_entry -= 2; + } + } + if (status == SWITCH_STATUS_TIMEOUT) { + goto end; + } + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + goto end; + } + } + +end: + switch_safe_free(sql); + return status; + +} + +SWITCH_STANDARD_APP(directory_function) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + int argc = 0; + char *argv[6] = { 0 }; + char *mydata = NULL; + const char *profile_name = NULL; + const char *domain_name = NULL; + dir_profile_t * profile = NULL; + int x = 0; + char *sql = NULL; + search_params_t s_param; + int attempts = 3; + char macro[255]; + + if (!switch_strlen_zero(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + if (argv[x]) { + profile_name = argv[x++]; + } + + if (argv[x]) { + domain_name = argv[x++]; + } + + + if (!(profile = get_profile(profile_name))) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error invalid profile %s\n", profile_name); + goto end; + } + + populate_database(session, profile, domain_name); + + memset(&s_param, 0, sizeof(s_param)); + s_param.search_by_last_name = 1; + s_param.try_again = 1; + switch_copy_string(s_param.profile, profile_name, 255); + switch_copy_string(s_param.domain, domain_name, 255); + + if (strcasecmp(profile->search_order, "last_name")) { + s_param.search_by_last_name = 0; + } + attempts = profile->max_menu_attempt; + s_param.try_again = 1; + while (switch_channel_ready(channel) && (s_param.try_again && attempts-- > 0)) { + s_param.try_again = 0; + gather_name_digit(session, profile, &s_param); + + if (switch_strlen_zero(s_param.digits)) { + s_param.try_again = 1; + continue; + } + + if (strlen(s_param.digits) < profile->min_search_digits) { + switch_snprintf(macro, sizeof(macro), "%d", profile->min_search_digits); + switch_ivr_phrase_macro(session, DIR_MIN_SEARCH_DIGITS, macro, NULL, NULL); + s_param.try_again = 1; + continue; + } + + navigate_entrys(session, profile, &s_param); + } + + if (!switch_strlen_zero(s_param.transfer_to)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Directory transfering call to : %s\n", s_param.transfer_to); + switch_ivr_session_transfer(session, s_param.transfer_to, "XML", domain_name); + } + +end: + /* Delete all sql entry for this call */ + sql = switch_mprintf("delete from directory_search where hostname = '%q' and uuid = '%q'", globals.hostname, switch_core_session_get_uuid(session)); + directory_execute_sql(sql, profile->mutex); + switch_safe_free(sql); + + + profile_rwunlock(profile); + +} + +SWITCH_MODULE_LOAD_FUNCTION(mod_directory_load) +{ + switch_application_interface_t *app_interface; + switch_status_t status; + switch_core_db_t *db = NULL; + char *sql = NULL; + + memset(&globals, 0, sizeof(globals)); + globals.pool = pool; + + switch_core_hash_init(&globals.profile_hash, globals.pool); + switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); + + if ((status = load_config(SWITCH_FALSE)) != SWITCH_STATUS_SUCCESS) { + return status; + } + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + gethostname(globals.hostname, sizeof(globals.hostname)); + + globals.dbname = switch_core_sprintf(pool, "directory"); + + if ((db = switch_core_db_open_file(globals.dbname))) { + switch_core_db_test_reactive(db, "select count(uuid),name_visible from directory_search", "drop table directory_search", dir_sql); + switch_core_db_close(db); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to open db name : %s\n", globals.dbname); + return SWITCH_STATUS_FALSE; + + } + + sql = switch_mprintf("delete from directory_search where hostname = '%q'", globals.hostname); + directory_execute_sql(sql, globals.mutex); + switch_safe_free(sql); + + SWITCH_ADD_APP(app_interface, "directory", "directory", DIR_DESC, directory_function, DIR_USAGE, SAF_NONE); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/* + Called when the system shuts down + Macro expands to: switch_status_t mod_directory_shutdown() */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_directory_shutdown) +{ + switch_hash_index_t *hi; + dir_profile_t *profile; + void *val = NULL; + const void *key; + switch_ssize_t keylen; + char *sql = NULL; + + switch_mutex_lock(globals.mutex); + + while((hi = switch_hash_first(NULL, globals.profile_hash))) { + switch_hash_this(hi, &key, &keylen, &val); + profile = (dir_profile_t *) val; + + switch_core_hash_delete(globals.profile_hash, profile->name); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for write lock (Profile %s)\n", profile->name); + switch_thread_rwlock_wrlock(profile->rwlock); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying Profile %s\n", profile->name); + switch_core_destroy_memory_pool(&profile->pool); + profile = NULL; + } + + sql = switch_mprintf("delete from directory_search where hostname = '%q'", globals.hostname); + directory_execute_sql(sql, globals.mutex); + switch_safe_free(sql); + + switch_mutex_unlock(globals.mutex); + return SWITCH_STATUS_SUCCESS; +} + +/* 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 + */ diff --git a/src/mod/applications/mod_directory/mod_directory.vcproj b/src/mod/applications/mod_directory/mod_directory.vcproj new file mode 100644 index 0000000000..8e043d1df4 --- /dev/null +++ b/src/mod/applications/mod_directory/mod_directory.vcproj @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +