Mon Oct 8 12:38:55 2012

Asterisk developer's documentation


app_directory.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Provide a directory of extensions
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  *
00025  * \ingroup applications
00026  */
00027 
00028 /*** MODULEINFO
00029    <depend>app_voicemail</depend>
00030    <support_level>core</support_level>
00031  ***/
00032 #include "asterisk.h"
00033 
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 368738 $")
00035 
00036 #include <ctype.h>
00037 
00038 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
00039 #include "asterisk/file.h"
00040 #include "asterisk/pbx.h"
00041 #include "asterisk/module.h"
00042 #include "asterisk/say.h"
00043 #include "asterisk/app.h"
00044 #include "asterisk/utils.h"
00045 
00046 /*** DOCUMENTATION
00047    <application name="Directory" language="en_US">
00048       <synopsis>
00049          Provide directory of voicemail extensions.
00050       </synopsis>
00051       <syntax>
00052          <parameter name="vm-context">
00053             <para>This is the context within voicemail.conf to use for the Directory. If not 
00054             specified and <literal>searchcontexts=no</literal> in 
00055             <filename>voicemail.conf</filename>, then <literal>default</literal> 
00056             will be assumed.</para>
00057          </parameter>
00058          <parameter name="dial-context" required="false">
00059             <para>This is the dialplan context to use when looking for an
00060             extension that the user has selected, or when jumping to the
00061             <literal>o</literal> or <literal>a</literal> extension. If not
00062             specified, the current context will be used.</para>
00063          </parameter>
00064          <parameter name="options" required="false">
00065             <optionlist>
00066                <option name="e">
00067                   <para>In addition to the name, also read the extension number to the
00068                   caller before presenting dialing options.</para>
00069                </option>
00070                <option name="f">
00071                   <para>Allow the caller to enter the first name of a user in the
00072                   directory instead of using the last name.  If specified, the
00073                   optional number argument will be used for the number of
00074                   characters the user should enter.</para>
00075                   <argument name="n" required="true" />
00076                </option>
00077                <option name="l">
00078                   <para>Allow the caller to enter the last name of a user in the
00079                   directory.  This is the default.  If specified, the
00080                   optional number argument will be used for the number of
00081                   characters the user should enter.</para>
00082                   <argument name="n" required="true" />
00083                </option>
00084                <option name="b">
00085                   <para> Allow the caller to enter either the first or the last name
00086                   of a user in the directory.  If specified, the optional number
00087                   argument will be used for the number of characters the user should enter.</para>
00088                   <argument name="n" required="true" />
00089                </option>
00090                <option name="m">
00091                   <para>Instead of reading each name sequentially and asking for
00092                   confirmation, create a menu of up to 8 names.</para>
00093                </option>
00094                <option name="n">
00095                   <para>Read digits even if the channel is not answered.</para>
00096                </option>
00097                <option name="p">
00098                   <para>Pause for n milliseconds after the digits are typed.  This is
00099                   helpful for people with cellphones, who are not holding the
00100                   receiver to their ear while entering DTMF.</para>
00101                   <argument name="n" required="true" />
00102                </option>
00103             </optionlist>
00104             <note><para>Only one of the <replaceable>f</replaceable>, <replaceable>l</replaceable>, or <replaceable>b</replaceable>
00105             options may be specified. <emphasis>If more than one is specified</emphasis>, then Directory will act as 
00106             if <replaceable>b</replaceable> was specified.  The number
00107             of characters for the user to type defaults to <literal>3</literal>.</para></note>
00108          </parameter>
00109       </syntax>
00110       <description>
00111          <para>This application will present the calling channel with a directory of extensions from which they can search
00112          by name. The list of names and corresponding extensions is retrieved from the
00113          voicemail configuration file, <filename>voicemail.conf</filename>.</para>
00114          <para>This application will immediately exit if one of the following DTMF digits are
00115          received and the extension to jump to exists:</para>
00116          <para><literal>0</literal> - Jump to the 'o' extension, if it exists.</para>
00117          <para><literal>*</literal> - Jump to the 'a' extension, if it exists.</para>
00118       </description>
00119    </application>
00120 
00121  ***/
00122 static const char app[] = "Directory";
00123 
00124 /* For simplicity, I'm keeping the format compatible with the voicemail config,
00125    but i'm open to suggestions for isolating it */
00126 
00127 #define VOICEMAIL_CONFIG "voicemail.conf"
00128 
00129 enum {
00130    OPT_LISTBYFIRSTNAME = (1 << 0),
00131    OPT_SAYEXTENSION =    (1 << 1),
00132    OPT_FROMVOICEMAIL =   (1 << 2),
00133    OPT_SELECTFROMMENU =  (1 << 3),
00134    OPT_LISTBYLASTNAME =  (1 << 4),
00135    OPT_LISTBYEITHER =    OPT_LISTBYFIRSTNAME | OPT_LISTBYLASTNAME,
00136    OPT_PAUSE =           (1 << 5),
00137    OPT_NOANSWER =        (1 << 6),
00138 };
00139 
00140 enum {
00141    OPT_ARG_FIRSTNAME =   0,
00142    OPT_ARG_LASTNAME =    1,
00143    OPT_ARG_EITHER =      2,
00144    OPT_ARG_PAUSE =       3,
00145    /* This *must* be the last value in this enum! */
00146    OPT_ARG_ARRAY_SIZE =  4,
00147 };
00148 
00149 struct directory_item {
00150    char exten[AST_MAX_EXTENSION + 1];
00151    char name[AST_MAX_EXTENSION + 1];
00152    char context[AST_MAX_CONTEXT + 1];
00153    char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
00154 
00155    AST_LIST_ENTRY(directory_item) entry;
00156 };
00157 
00158 AST_APP_OPTIONS(directory_app_options, {
00159    AST_APP_OPTION_ARG('f', OPT_LISTBYFIRSTNAME, OPT_ARG_FIRSTNAME),
00160    AST_APP_OPTION_ARG('l', OPT_LISTBYLASTNAME, OPT_ARG_LASTNAME),
00161    AST_APP_OPTION_ARG('b', OPT_LISTBYEITHER, OPT_ARG_EITHER),
00162    AST_APP_OPTION_ARG('p', OPT_PAUSE, OPT_ARG_PAUSE),
00163    AST_APP_OPTION('e', OPT_SAYEXTENSION),
00164    AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
00165    AST_APP_OPTION('m', OPT_SELECTFROMMENU),
00166    AST_APP_OPTION('n', OPT_NOANSWER),
00167 });
00168 
00169 static int compare(const char *text, const char *template)
00170 {
00171    char digit;
00172 
00173    if (ast_strlen_zero(text)) {
00174       return -1;
00175    }
00176 
00177    while (*template) {
00178       digit = toupper(*text++);
00179       switch (digit) {
00180       case 0:
00181          return -1;
00182       case '1':
00183          digit = '1';
00184          break;
00185       case '2':
00186       case 'A':
00187       case 'B':
00188       case 'C':
00189          digit = '2';
00190          break;
00191       case '3':
00192       case 'D':
00193       case 'E':
00194       case 'F':
00195          digit = '3';
00196          break;
00197       case '4':
00198       case 'G':
00199       case 'H':
00200       case 'I':
00201          digit = '4';
00202          break;
00203       case '5':
00204       case 'J':
00205       case 'K':
00206       case 'L':
00207          digit = '5';
00208          break;
00209       case '6':
00210       case 'M':
00211       case 'N':
00212       case 'O':
00213          digit = '6';
00214          break;
00215       case '7':
00216       case 'P':
00217       case 'Q':
00218       case 'R':
00219       case 'S':
00220          digit = '7';
00221          break;
00222       case '8':
00223       case 'T':
00224       case 'U':
00225       case 'V':
00226          digit = '8';
00227          break;
00228       case '9':
00229       case 'W':
00230       case 'X':
00231       case 'Y':
00232       case 'Z':
00233          digit = '9';
00234          break;
00235 
00236       default:
00237          if (digit > ' ')
00238             return -1;
00239          continue;
00240       }
00241 
00242       if (*template++ != digit)
00243          return -1;
00244    }
00245 
00246    return 0;
00247 }
00248 
00249 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
00250 {
00251    if (!ast_goto_if_exists(chan, S_OR(dialcontext, chan->context), ext, 1) ||
00252       (!ast_strlen_zero(chan->macrocontext) &&
00253       !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
00254       return 0;
00255    } else {
00256       ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
00257          "Not Exiting the Directory!\n", ext);
00258       return -1;
00259    }
00260 }
00261 
00262 /* play name of mailbox owner.
00263  * returns:  -1 for bad or missing extension
00264  *           '1' for selected entry from directory
00265  *           '*' for skipped entry from directory
00266  */
00267 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
00268    const char *ext, const char *name, struct ast_flags *flags)
00269 {
00270    int res = 0;
00271    if ((res = ast_app_sayname(chan, ext, context)) >= 0) {
00272       ast_stopstream(chan);
00273       /* If Option 'e' was specified, also read the extension number with the name */
00274       if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
00275          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00276          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
00277       }
00278    } else {
00279       res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
00280       if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
00281          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00282          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
00283       }
00284    }
00285 
00286    return res;
00287 }
00288 
00289 static int select_entry(struct ast_channel *chan, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
00290 {
00291    ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, S_OR(dialcontext, item->context));
00292 
00293    if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
00294       /* We still want to set the exten though */
00295       ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
00296    } else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
00297       ast_log(LOG_WARNING,
00298          "Can't find extension '%s' in context '%s'.  "
00299          "Did you pass the wrong context to Directory?\n",
00300          item->exten, S_OR(dialcontext, item->context));
00301       return -1;
00302    }
00303 
00304    return 0;
00305 }
00306 
00307 static int select_item_pause(struct ast_channel *chan, struct ast_flags *flags, char *opts[])
00308 {
00309    int res = 0, opt_pause = 0;
00310 
00311    if (ast_test_flag(flags, OPT_PAUSE) && !ast_strlen_zero(opts[OPT_ARG_PAUSE])) {
00312       opt_pause = atoi(opts[OPT_ARG_PAUSE]);
00313       if (opt_pause > 3000) {
00314          opt_pause = 3000;
00315       }
00316       res = ast_waitfordigit(chan, opt_pause);
00317    }
00318    return res;
00319 }
00320 
00321 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
00322 {
00323    struct directory_item *item, **ptr;
00324    int i, res, loop;
00325 
00326    /* option p(n): cellphone pause option */
00327    /* allow early press of selection key */
00328    res = select_item_pause(chan, flags, opts);
00329 
00330    for (ptr = items, i = 0; i < count; i++, ptr++) {
00331       item = *ptr;
00332 
00333       for (loop = 3 ; loop > 0; loop--) {
00334          if (!res)
00335             res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
00336          if (!res)
00337             res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
00338          if (!res)
00339             res = ast_waitfordigit(chan, 3000);
00340          ast_stopstream(chan);
00341    
00342          if (res == '0') { /* operator selected */
00343             goto_exten(chan, dialcontext, "o");
00344             return '0';
00345          } else if (res == '1') { /* Name selected */
00346             return select_entry(chan, dialcontext, item, flags) ? -1 : 1;
00347          } else if (res == '*') {
00348             /* Skip to next match in list */
00349             break;
00350          } else if (res == '#') {
00351             /* Exit reading, continue in dialplan */
00352             return res;
00353          }
00354 
00355          if (res < 0)
00356             return -1;
00357 
00358          res = 0;
00359       }
00360       res = 0;
00361    }
00362 
00363    /* Nothing was selected */
00364    return 0;
00365 }
00366 
00367 static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
00368 {
00369    struct directory_item **block, *item;
00370    int i, limit, res = 0;
00371    char buf[9];
00372 
00373    /* option p(n): cellphone pause option */
00374    select_item_pause(chan, flags, opts);
00375 
00376    for (block = items; count; block += limit, count -= limit) {
00377       limit = count;
00378       if (limit > 8)
00379          limit = 8;
00380 
00381       for (i = 0; i < limit && !res; i++) {
00382          item = block[i];
00383 
00384          snprintf(buf, sizeof(buf), "digits/%d", i + 1);
00385          /* Press <num> for <name>, [ extension <ext> ] */
00386          res = ast_streamfile(chan, "dir-multi1", chan->language);
00387          if (!res)
00388             res = ast_waitstream(chan, AST_DIGIT_ANY);
00389          if (!res)
00390             res = ast_streamfile(chan, buf, chan->language);
00391          if (!res)
00392             res = ast_waitstream(chan, AST_DIGIT_ANY);
00393          if (!res)
00394             res = ast_streamfile(chan, "dir-multi2", chan->language);
00395          if (!res)
00396             res = ast_waitstream(chan, AST_DIGIT_ANY);
00397          if (!res)
00398             res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
00399          if (!res)
00400             res = ast_waitstream(chan, AST_DIGIT_ANY);
00401          if (!res)
00402             res = ast_waitfordigit(chan, 800);
00403       }
00404 
00405       /* Press "9" for more names. */
00406       if (!res && count > limit) {
00407          res = ast_streamfile(chan, "dir-multi9", chan->language);
00408          if (!res)
00409             res = ast_waitstream(chan, AST_DIGIT_ANY);
00410       }
00411 
00412       if (!res) {
00413          res = ast_waitfordigit(chan, 3000);
00414       }
00415 
00416       if (res && res > '0' && res < '1' + limit) {
00417          return select_entry(chan, dialcontext, block[res - '1'], flags) ? -1 : 1;
00418       }
00419 
00420       if (res < 0)
00421          return -1;
00422 
00423       res = 0;
00424    }
00425 
00426    /* Nothing was selected */
00427    return 0;
00428 }
00429 
00430 static struct ast_config *realtime_directory(char *context)
00431 {
00432    struct ast_config *cfg;
00433    struct ast_config *rtdata;
00434    struct ast_category *cat;
00435    struct ast_variable *var;
00436    char *mailbox;
00437    const char *fullname;
00438    const char *hidefromdir, *searchcontexts = NULL;
00439    char tmp[100];
00440    struct ast_flags config_flags = { 0 };
00441 
00442    /* Load flat file config. */
00443    cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
00444 
00445    if (!cfg) {
00446       /* Loading config failed. */
00447       ast_log(LOG_WARNING, "Loading config failed.\n");
00448       return NULL;
00449    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00450       ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", VOICEMAIL_CONFIG);
00451       return NULL;
00452    }
00453 
00454    /* Get realtime entries, categorized by their mailbox number
00455       and present in the requested context */
00456    if (ast_strlen_zero(context) && (searchcontexts = ast_variable_retrieve(cfg, "general", "searchcontexts"))) {
00457       if (ast_true(searchcontexts)) {
00458          rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", SENTINEL);
00459          context = NULL;
00460       } else {
00461          rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", "default", SENTINEL);
00462          context = "default";
00463       }
00464    } else {
00465       rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
00466    }
00467 
00468    /* if there are no results, just return the entries from the config file */
00469    if (!rtdata) {
00470       return cfg;
00471    }
00472 
00473    mailbox = NULL;
00474    while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
00475       const char *context = ast_variable_retrieve(rtdata, mailbox, "context");
00476 
00477       fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
00478       hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
00479       if (ast_true(hidefromdir)) {
00480          /* Skip hidden */
00481          continue;
00482       }
00483       snprintf(tmp, sizeof(tmp), "no-password,%s", S_OR(fullname, ""));
00484 
00485       /* Does the context exist within the config file? If not, make one */
00486       if (!(cat = ast_category_get(cfg, context))) {
00487          if (!(cat = ast_category_new(context, "", 99999))) {
00488             ast_log(LOG_WARNING, "Out of memory\n");
00489             ast_config_destroy(cfg);
00490             if (rtdata) {
00491                ast_config_destroy(rtdata);
00492             }
00493             return NULL;
00494          }
00495          ast_category_append(cfg, cat);
00496       }
00497 
00498       if ((var = ast_variable_new(mailbox, tmp, ""))) {
00499          ast_variable_append(cat, var);
00500       } else {
00501          ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
00502       }
00503    }
00504    ast_config_destroy(rtdata);
00505 
00506    return cfg;
00507 }
00508 
00509 static int check_match(struct directory_item **result, const char *item_context, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
00510 {
00511    struct directory_item *item;
00512    const char *key = NULL;
00513    int namelen;
00514 
00515    if (ast_strlen_zero(item_fullname)) {
00516       return 0;
00517    }
00518 
00519    /* Set key to last name or first name depending on search mode */
00520    if (!use_first_name)
00521       key = strchr(item_fullname, ' ');
00522 
00523    if (key)
00524       key++;
00525    else
00526       key = item_fullname;
00527 
00528    if (compare(key, pattern_ext))
00529       return 0;
00530 
00531    ast_debug(1, "Found match %s@%s\n", item_ext, item_context);
00532 
00533    /* Match */
00534    item = ast_calloc(1, sizeof(*item));
00535    if (!item)
00536       return -1;
00537    ast_copy_string(item->context, item_context, sizeof(item->context));
00538    ast_copy_string(item->name, item_fullname, sizeof(item->name));
00539    ast_copy_string(item->exten, item_ext, sizeof(item->exten));
00540 
00541    ast_copy_string(item->key, key, sizeof(item->key));
00542    if (key != item_fullname) {
00543       /* Key is the last name. Append first name to key in order to sort Last,First */
00544       namelen = key - item_fullname - 1;
00545       if (namelen > sizeof(item->key) - strlen(item->key) - 1)
00546          namelen = sizeof(item->key) - strlen(item->key) - 1;
00547       strncat(item->key, item_fullname, namelen);
00548    }
00549 
00550    *result = item;
00551    return 1;
00552 }
00553 
00554 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
00555 
00556 static int search_directory_sub(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
00557 {
00558    struct ast_variable *v;
00559    char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
00560    struct directory_item *item;
00561    int res;
00562 
00563    ast_debug(2, "Pattern: %s\n", ext);
00564 
00565    for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
00566 
00567       /* Ignore hidden */
00568       if (strcasestr(v->value, "hidefromdir=yes"))
00569          continue;
00570 
00571       ast_copy_string(buf, v->value, sizeof(buf));
00572       bufptr = buf;
00573 
00574       /* password,Full Name,email,pager,options */
00575       strsep(&bufptr, ",");
00576       pos = strsep(&bufptr, ",");
00577 
00578       /* No name to compare against */
00579       if (ast_strlen_zero(pos)) {
00580          continue;
00581       }
00582 
00583       res = 0;
00584       if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00585          res = check_match(&item, context, pos, v->name, ext, 0 /* use_first_name */);
00586       }
00587       if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00588          res = check_match(&item, context, pos, v->name, ext, 1 /* use_first_name */);
00589       }
00590 
00591       if (!res)
00592          continue;
00593       else if (res < 0)
00594          return -1;
00595 
00596       AST_LIST_INSERT_TAIL(alist, item, entry);
00597    }
00598 
00599    if (ucfg) {
00600       for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
00601          const char *position;
00602          if (!strcasecmp(cat, "general"))
00603             continue;
00604          if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
00605             continue;
00606 
00607          /* Find all candidate extensions */
00608          position = ast_variable_retrieve(ucfg, cat, "fullname");
00609          if (!position)
00610             continue;
00611 
00612          res = 0;
00613          if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00614             res = check_match(&item, context, position, cat, ext, 0 /* use_first_name */);
00615          }
00616          if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00617             res = check_match(&item, context, position, cat, ext, 1 /* use_first_name */);
00618          }
00619 
00620          if (!res)
00621             continue;
00622          else if (res < 0)
00623             return -1;
00624 
00625          AST_LIST_INSERT_TAIL(alist, item, entry);
00626       }
00627    }
00628    return 0;
00629 }
00630 
00631 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
00632 {
00633    const char *searchcontexts = ast_variable_retrieve(vmcfg, "general", "searchcontexts");
00634    if (ast_strlen_zero(context)) {
00635       if (!ast_strlen_zero(searchcontexts) && ast_true(searchcontexts)) {
00636          /* Browse each context for a match */
00637          int res;
00638          const char *catg;
00639          for (catg = ast_category_browse(vmcfg, NULL); catg; catg = ast_category_browse(vmcfg, catg)) {
00640             if (!strcmp(catg, "general") || !strcmp(catg, "zonemessages")) {
00641                continue;
00642             }
00643 
00644             if ((res = search_directory_sub(catg, vmcfg, ucfg, ext, flags, alist))) {
00645                return res;
00646             }
00647          }
00648          return 0;
00649       } else {
00650          ast_debug(1, "Searching by category default\n");
00651          return search_directory_sub("default", vmcfg, ucfg, ext, flags, alist);
00652       }
00653    } else {
00654       /* Browse only the listed context for a match */
00655       ast_debug(1, "Searching by category %s\n", context);
00656       return search_directory_sub(context, vmcfg, ucfg, ext, flags, alist);
00657    }
00658 }
00659 
00660 static void sort_items(struct directory_item **sorted, int count)
00661 {
00662    int reordered, i;
00663    struct directory_item **ptr, *tmp;
00664 
00665    if (count < 2)
00666       return;
00667 
00668    /* Bubble-sort items by the key */
00669    do {
00670       reordered = 0;
00671       for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
00672          if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
00673             tmp = ptr[0];
00674             ptr[0] = ptr[1];
00675             ptr[1] = tmp;
00676             reordered++;
00677          }
00678       }
00679    } while (reordered);
00680 }
00681 
00682 static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, int digits, struct ast_flags *flags, char *opts[])
00683 {
00684    /* Read in the first three digits..  "digit" is the first digit, already read */
00685    int res = 0;
00686    itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
00687    struct directory_item *item, **ptr, **sorted = NULL;
00688    int count, i;
00689    char ext[10] = "";
00690 
00691    if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
00692       return digit;
00693    }
00694 
00695    if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
00696       return digit;
00697    }
00698 
00699    ext[0] = digit;
00700    if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
00701       return -1;
00702 
00703    res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
00704    if (res)
00705       goto exit;
00706 
00707    /* Count items in the list */
00708    count = 0;
00709    AST_LIST_TRAVERSE(&alist, item, entry) {
00710       count++;
00711    }
00712 
00713    if (count < 1) {
00714       res = ast_streamfile(chan, "dir-nomatch", chan->language);
00715       goto exit;
00716    }
00717 
00718 
00719    /* Create plain array of pointers to items (for sorting) */
00720    sorted = ast_calloc(count, sizeof(*sorted));
00721 
00722    ptr = sorted;
00723    AST_LIST_TRAVERSE(&alist, item, entry) {
00724       *ptr++ = item;
00725    }
00726 
00727    /* Sort items */
00728    sort_items(sorted, count);
00729 
00730    if (option_debug) {
00731       ast_debug(2, "Listing matching entries:\n");
00732       for (ptr = sorted, i = 0; i < count; i++, ptr++) {
00733          ast_debug(2, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
00734       }
00735    }
00736 
00737    if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
00738       /* Offer multiple entries at the same time */
00739       res = select_item_menu(chan, sorted, count, dialcontext, flags, opts);
00740    } else {
00741       /* Offer entries one by one */
00742       res = select_item_seq(chan, sorted, count, dialcontext, flags, opts);
00743    }
00744 
00745    if (!res) {
00746       res = ast_streamfile(chan, "dir-nomore", chan->language);
00747    }
00748 
00749 exit:
00750    if (sorted)
00751       ast_free(sorted);
00752 
00753    while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
00754       ast_free(item);
00755 
00756    return res;
00757 }
00758 
00759 static int directory_exec(struct ast_channel *chan, const char *data)
00760 {
00761    int res = 0, digit = 3;
00762    struct ast_config *cfg, *ucfg;
00763    const char *dirintro;
00764    char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { 0, };
00765    struct ast_flags flags = { 0 };
00766    struct ast_flags config_flags = { 0 };
00767    enum { FIRST, LAST, BOTH } which = LAST;
00768    char digits[9] = "digits/3";
00769    AST_DECLARE_APP_ARGS(args,
00770       AST_APP_ARG(vmcontext);
00771       AST_APP_ARG(dialcontext);
00772       AST_APP_ARG(options);
00773    );
00774 
00775    parse = ast_strdupa(data);
00776 
00777    AST_STANDARD_APP_ARGS(args, parse);
00778 
00779    if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
00780       return -1;
00781 
00782    if (!(cfg = realtime_directory(args.vmcontext))) {
00783       ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
00784       return -1;
00785    }
00786 
00787    if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) {
00788       ast_log(LOG_ERROR, "Config file users.conf is in an invalid format.  Aborting.\n");
00789       ucfg = NULL;
00790    }
00791 
00792    dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
00793    if (ast_strlen_zero(dirintro))
00794       dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
00795    /* the above prompts probably should be modified to include 0 for dialing operator
00796       and # for exiting (continues in dialplan) */
00797 
00798    if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00799       if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
00800          digit = atoi(opts[OPT_ARG_EITHER]);
00801       }
00802       which = BOTH;
00803    } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00804       if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
00805          digit = atoi(opts[OPT_ARG_FIRSTNAME]);
00806       }
00807       which = FIRST;
00808    } else {
00809       if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
00810          digit = atoi(opts[OPT_ARG_LASTNAME]);
00811       }
00812       which = LAST;
00813    }
00814 
00815    /* If no options specified, search by last name */
00816    if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00817       ast_set_flag(&flags, OPT_LISTBYLASTNAME);
00818       which = LAST;
00819    }
00820 
00821    if (digit > 9) {
00822       digit = 9;
00823    } else if (digit < 1) {
00824       digit = 3;
00825    }
00826    digits[7] = digit + '0';
00827 
00828    if (chan->_state != AST_STATE_UP) {
00829       if (!ast_test_flag(&flags, OPT_NOANSWER)) {
00830          /* Otherwise answer unless we're supposed to read while on-hook */
00831          res = ast_answer(chan);
00832       }
00833    }
00834    for (;;) {
00835       if (!ast_strlen_zero(dirintro) && !res) {
00836          res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
00837       } else if (!res) {
00838          /* Stop playing sounds as soon as we have a digit. */
00839          res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
00840          if (!res) {
00841             res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
00842          }
00843          if (!res) {
00844             res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
00845          }
00846          if (!res) {
00847             res = ast_stream_and_wait(chan, 
00848                which == FIRST ? "dir-first" :
00849                which == LAST ? "dir-last" :
00850                "dir-firstlast", AST_DIGIT_ANY);
00851          }
00852          if (!res) {
00853             res = ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
00854          }
00855       }
00856       ast_stopstream(chan);
00857       if (!res)
00858          res = ast_waitfordigit(chan, 5000);
00859 
00860       if (res <= 0)
00861          break;
00862 
00863       res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags, opts);
00864       if (res)
00865          break;
00866 
00867       res = ast_waitstream(chan, AST_DIGIT_ANY);
00868       ast_stopstream(chan);
00869 
00870       if (res)
00871          break;
00872    }
00873 
00874    if (ucfg)
00875       ast_config_destroy(ucfg);
00876    ast_config_destroy(cfg);
00877 
00878    return res < 0 ? -1 : 0;
00879 }
00880 
00881 static int unload_module(void)
00882 {
00883    int res;
00884    res = ast_unregister_application(app);
00885    return res;
00886 }
00887 
00888 static int load_module(void)
00889 {
00890    return ast_register_application_xml(app, directory_exec);
00891 }
00892 
00893 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");

Generated on Mon Oct 8 12:38:55 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7