Mon Sep 20 2010 00:19:44

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