Thu Apr 28 2011 17:13:28

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: 263809 $")
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 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
00243 {
00244    if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
00245       (!ast_strlen_zero(chan->macrocontext) &&
00246       !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
00247       return 0;
00248    } else {
00249       ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
00250          "Not Exiting the Directory!\n", ext);
00251       return -1;
00252    }
00253 }
00254 
00255 /* play name of mailbox owner.
00256  * returns:  -1 for bad or missing extension
00257  *           '1' for selected entry from directory
00258  *           '*' for skipped entry from directory
00259  */
00260 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
00261    const char *ext, const char *name, struct ast_flags *flags)
00262 {
00263    int res = 0;
00264    if ((res = ast_app_sayname(chan, ext, context)) >= 0) {
00265       ast_stopstream(chan);
00266       /* If Option 'e' was specified, also read the extension number with the name */
00267       if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
00268          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00269          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
00270       }
00271    } else {
00272       res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
00273       if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
00274          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00275          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
00276       }
00277    }
00278 
00279    return res;
00280 }
00281 
00282 static int select_entry(struct ast_channel *chan, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
00283 {
00284    ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, S_OR(dialcontext, item->context));
00285 
00286    if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
00287       /* We still want to set the exten though */
00288       ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
00289    } else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
00290       ast_log(LOG_WARNING,
00291          "Can't find extension '%s' in context '%s'.  "
00292          "Did you pass the wrong context to Directory?\n",
00293          item->exten, S_OR(dialcontext, item->context));
00294       return -1;
00295    }
00296 
00297    return 0;
00298 }
00299 
00300 static int select_item_pause(struct ast_channel *chan, struct ast_flags *flags, char *opts[])
00301 {
00302    int res = 0, opt_pause = 0;
00303 
00304    if (ast_test_flag(flags, OPT_PAUSE) && !ast_strlen_zero(opts[OPT_ARG_PAUSE])) {
00305       opt_pause = atoi(opts[OPT_ARG_PAUSE]);
00306       if (opt_pause > 3000) {
00307          opt_pause = 3000;
00308       }
00309       res = ast_waitfordigit(chan, opt_pause);
00310    }
00311    return res;
00312 }
00313 
00314 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
00315 {
00316    struct directory_item *item, **ptr;
00317    int i, res, loop;
00318 
00319    /* option p(n): cellphone pause option */
00320    /* allow early press of selection key */
00321    res = select_item_pause(chan, flags, opts);
00322 
00323    for (ptr = items, i = 0; i < count; i++, ptr++) {
00324       item = *ptr;
00325 
00326       for (loop = 3 ; loop > 0; loop--) {
00327          if (!res)
00328             res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
00329          if (!res)
00330             res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
00331          if (!res)
00332             res = ast_waitfordigit(chan, 3000);
00333          ast_stopstream(chan);
00334    
00335          if (res == '0') { /* operator selected */
00336             goto_exten(chan, dialcontext, "o");
00337             return '0';
00338          } else if (res == '1') { /* Name selected */
00339             return select_entry(chan, dialcontext, item, flags) ? -1 : 1;
00340          } else if (res == '*') {
00341             /* Skip to next match in list */
00342             break;
00343          } else if (res == '#') {
00344             /* Exit reading, continue in dialplan */
00345             return res;
00346          }
00347 
00348          if (res < 0)
00349             return -1;
00350 
00351          res = 0;
00352       }
00353       res = 0;
00354    }
00355 
00356    /* Nothing was selected */
00357    return 0;
00358 }
00359 
00360 static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
00361 {
00362    struct directory_item **block, *item;
00363    int i, limit, res = 0;
00364    char buf[9];
00365 
00366    /* option p(n): cellphone pause option */
00367    select_item_pause(chan, flags, opts);
00368 
00369    for (block = items; count; block += limit, count -= limit) {
00370       limit = count;
00371       if (limit > 8)
00372          limit = 8;
00373 
00374       for (i = 0; i < limit && !res; i++) {
00375          item = block[i];
00376 
00377          snprintf(buf, sizeof(buf), "digits/%d", i + 1);
00378          /* Press <num> for <name>, [ extension <ext> ] */
00379          res = ast_streamfile(chan, "dir-multi1", chan->language);
00380          if (!res)
00381             res = ast_waitstream(chan, AST_DIGIT_ANY);
00382          if (!res)
00383             res = ast_streamfile(chan, buf, chan->language);
00384          if (!res)
00385             res = ast_waitstream(chan, AST_DIGIT_ANY);
00386          if (!res)
00387             res = ast_streamfile(chan, "dir-multi2", chan->language);
00388          if (!res)
00389             res = ast_waitstream(chan, AST_DIGIT_ANY);
00390          if (!res)
00391             res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
00392          if (!res)
00393             res = ast_waitstream(chan, AST_DIGIT_ANY);
00394          if (!res)
00395             res = ast_waitfordigit(chan, 800);
00396       }
00397 
00398       /* Press "9" for more names. */
00399       if (!res && count > limit) {
00400          res = ast_streamfile(chan, "dir-multi9", chan->language);
00401          if (!res)
00402             res = ast_waitstream(chan, AST_DIGIT_ANY);
00403       }
00404 
00405       if (!res) {
00406          res = ast_waitfordigit(chan, 3000);
00407       }
00408 
00409       if (res && res > '0' && res < '1' + limit) {
00410          return select_entry(chan, dialcontext, block[res - '1'], flags) ? -1 : 1;
00411       }
00412 
00413       if (res < 0)
00414          return -1;
00415 
00416       res = 0;
00417    }
00418 
00419    /* Nothing was selected */
00420    return 0;
00421 }
00422 
00423 static struct ast_config *realtime_directory(char *context)
00424 {
00425    struct ast_config *cfg;
00426    struct ast_config *rtdata;
00427    struct ast_category *cat;
00428    struct ast_variable *var;
00429    char *mailbox;
00430    const char *fullname;
00431    const char *hidefromdir, *searchcontexts = NULL;
00432    char tmp[100];
00433    struct ast_flags config_flags = { 0 };
00434 
00435    /* Load flat file config. */
00436    cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
00437 
00438    if (!cfg) {
00439       /* Loading config failed. */
00440       ast_log(LOG_WARNING, "Loading config failed.\n");
00441       return NULL;
00442    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00443       ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", VOICEMAIL_CONFIG);
00444       return NULL;
00445    }
00446 
00447    /* Get realtime entries, categorized by their mailbox number
00448       and present in the requested context */
00449    if (ast_strlen_zero(context) && (searchcontexts = ast_variable_retrieve(cfg, "general", "searchcontexts"))) {
00450       if (ast_true(searchcontexts)) {
00451          rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", SENTINEL);
00452          context = NULL;
00453       } else {
00454          rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", "default", SENTINEL);
00455          context = "default";
00456       }
00457    } else {
00458       rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
00459    }
00460 
00461    /* if there are no results, just return the entries from the config file */
00462    if (!rtdata) {
00463       return cfg;
00464    }
00465 
00466    mailbox = NULL;
00467    while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
00468       const char *context = ast_variable_retrieve(rtdata, mailbox, "context");
00469 
00470       fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
00471       if (ast_true((hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir")))) {
00472          /* Skip hidden */
00473          continue;
00474       }
00475       snprintf(tmp, sizeof(tmp), "no-password,%s", S_OR(fullname, ""));
00476 
00477       /* Does the context exist within the config file? If not, make one */
00478       if (!(cat = ast_category_get(cfg, context))) {
00479          if (!(cat = ast_category_new(context, "", 99999))) {
00480             ast_log(LOG_WARNING, "Out of memory\n");
00481             ast_config_destroy(cfg);
00482             if (rtdata) {
00483                ast_config_destroy(rtdata);
00484             }
00485             return NULL;
00486          }
00487          ast_category_append(cfg, cat);
00488       }
00489 
00490       if ((var = ast_variable_new(mailbox, tmp, ""))) {
00491          ast_variable_append(cat, var);
00492       } else {
00493          ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
00494       }
00495    }
00496    ast_config_destroy(rtdata);
00497 
00498    return cfg;
00499 }
00500 
00501 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)
00502 {
00503    struct directory_item *item;
00504    const char *key = NULL;
00505    int namelen;
00506 
00507    if (ast_strlen_zero(item_fullname)) {
00508       return 0;
00509    }
00510 
00511    /* Set key to last name or first name depending on search mode */
00512    if (!use_first_name)
00513       key = strchr(item_fullname, ' ');
00514 
00515    if (key)
00516       key++;
00517    else
00518       key = item_fullname;
00519 
00520    if (compare(key, pattern_ext))
00521       return 0;
00522 
00523    ast_debug(1, "Found match %s@%s\n", item_ext, item_context);
00524 
00525    /* Match */
00526    item = ast_calloc(1, sizeof(*item));
00527    if (!item)
00528       return -1;
00529    ast_copy_string(item->context, item_context, sizeof(item->context));
00530    ast_copy_string(item->name, item_fullname, sizeof(item->name));
00531    ast_copy_string(item->exten, item_ext, sizeof(item->exten));
00532 
00533    ast_copy_string(item->key, key, sizeof(item->key));
00534    if (key != item_fullname) {
00535       /* Key is the last name. Append first name to key in order to sort Last,First */
00536       namelen = key - item_fullname - 1;
00537       if (namelen > sizeof(item->key) - strlen(item->key) - 1)
00538          namelen = sizeof(item->key) - strlen(item->key) - 1;
00539       strncat(item->key, item_fullname, namelen);
00540    }
00541 
00542    *result = item;
00543    return 1;
00544 }
00545 
00546 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
00547 
00548 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)
00549 {
00550    struct ast_variable *v;
00551    char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
00552    struct directory_item *item;
00553    int res;
00554 
00555    ast_debug(2, "Pattern: %s\n", ext);
00556 
00557    for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
00558 
00559       /* Ignore hidden */
00560       if (strcasestr(v->value, "hidefromdir=yes"))
00561          continue;
00562 
00563       ast_copy_string(buf, v->value, sizeof(buf));
00564       bufptr = buf;
00565 
00566       /* password,Full Name,email,pager,options */
00567       strsep(&bufptr, ",");
00568       pos = strsep(&bufptr, ",");
00569 
00570       /* No name to compare against */
00571       if (ast_strlen_zero(pos)) {
00572          continue;
00573       }
00574 
00575       res = 0;
00576       if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00577          res = check_match(&item, context, pos, v->name, ext, 0 /* use_first_name */);
00578       }
00579       if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00580          res = check_match(&item, context, pos, v->name, ext, 1 /* use_first_name */);
00581       }
00582 
00583       if (!res)
00584          continue;
00585       else if (res < 0)
00586          return -1;
00587 
00588       AST_LIST_INSERT_TAIL(alist, item, entry);
00589    }
00590 
00591    if (ucfg) {
00592       for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
00593          const char *position;
00594          if (!strcasecmp(cat, "general"))
00595             continue;
00596          if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
00597             continue;
00598 
00599          /* Find all candidate extensions */
00600          position = ast_variable_retrieve(ucfg, cat, "fullname");
00601          if (!position)
00602             continue;
00603 
00604          res = 0;
00605          if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00606             res = check_match(&item, context, position, cat, ext, 0 /* use_first_name */);
00607          }
00608          if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00609             res = check_match(&item, context, position, cat, ext, 1 /* use_first_name */);
00610          }
00611 
00612          if (!res)
00613             continue;
00614          else if (res < 0)
00615             return -1;
00616 
00617          AST_LIST_INSERT_TAIL(alist, item, entry);
00618       }
00619    }
00620    return 0;
00621 }
00622 
00623 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
00624 {
00625    const char *searchcontexts = ast_variable_retrieve(vmcfg, "general", "searchcontexts");
00626    if (ast_strlen_zero(context)) {
00627       if (!ast_strlen_zero(searchcontexts) && ast_true(searchcontexts)) {
00628          /* Browse each context for a match */
00629          int res;
00630          const char *catg;
00631          for (catg = ast_category_browse(vmcfg, NULL); catg; catg = ast_category_browse(vmcfg, catg)) {
00632             if (!strcmp(catg, "general") || !strcmp(catg, "zonemessages")) {
00633                continue;
00634             }
00635 
00636             if ((res = search_directory_sub(catg, vmcfg, ucfg, ext, flags, alist))) {
00637                return res;
00638             }
00639          }
00640          return 0;
00641       } else {
00642          ast_debug(1, "Searching by category default\n");
00643          return search_directory_sub("default", vmcfg, ucfg, ext, flags, alist);
00644       }
00645    } else {
00646       /* Browse only the listed context for a match */
00647       ast_debug(1, "Searching by category %s\n", context);
00648       return search_directory_sub(context, vmcfg, ucfg, ext, flags, alist);
00649    }
00650 }
00651 
00652 static void sort_items(struct directory_item **sorted, int count)
00653 {
00654    int reordered, i;
00655    struct directory_item **ptr, *tmp;
00656 
00657    if (count < 2)
00658       return;
00659 
00660    /* Bubble-sort items by the key */
00661    do {
00662       reordered = 0;
00663       for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
00664          if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
00665             tmp = ptr[0];
00666             ptr[0] = ptr[1];
00667             ptr[1] = tmp;
00668             reordered++;
00669          }
00670       }
00671    } while (reordered);
00672 }
00673 
00674 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[])
00675 {
00676    /* Read in the first three digits..  "digit" is the first digit, already read */
00677    int res = 0;
00678    itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
00679    struct directory_item *item, **ptr, **sorted = NULL;
00680    int count, i;
00681    char ext[10] = "";
00682 
00683    if (digit == '0' && !goto_exten(chan, S_OR(dialcontext, "default"), "o")) {
00684       return digit;
00685    }
00686 
00687    if (digit == '*' && !goto_exten(chan, S_OR(dialcontext, "default"), "a")) {
00688       return digit;
00689    }
00690 
00691    ext[0] = digit;
00692    if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
00693       return -1;
00694 
00695    res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
00696    if (res)
00697       goto exit;
00698 
00699    /* Count items in the list */
00700    count = 0;
00701    AST_LIST_TRAVERSE(&alist, item, entry) {
00702       count++;
00703    }
00704 
00705    if (count < 1) {
00706       res = ast_streamfile(chan, "dir-nomatch", chan->language);
00707       goto exit;
00708    }
00709 
00710 
00711    /* Create plain array of pointers to items (for sorting) */
00712    sorted = ast_calloc(count, sizeof(*sorted));
00713 
00714    ptr = sorted;
00715    AST_LIST_TRAVERSE(&alist, item, entry) {
00716       *ptr++ = item;
00717    }
00718 
00719    /* Sort items */
00720    sort_items(sorted, count);
00721 
00722    if (option_debug) {
00723       ast_debug(2, "Listing matching entries:\n");
00724       for (ptr = sorted, i = 0; i < count; i++, ptr++) {
00725          ast_debug(2, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
00726       }
00727    }
00728 
00729    if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
00730       /* Offer multiple entries at the same time */
00731       res = select_item_menu(chan, sorted, count, dialcontext, flags, opts);
00732    } else {
00733       /* Offer entries one by one */
00734       res = select_item_seq(chan, sorted, count, dialcontext, flags, opts);
00735    }
00736 
00737    if (!res) {
00738       res = ast_streamfile(chan, "dir-nomore", chan->language);
00739    }
00740 
00741 exit:
00742    if (sorted)
00743       ast_free(sorted);
00744 
00745    while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
00746       ast_free(item);
00747 
00748    return res;
00749 }
00750 
00751 static int directory_exec(struct ast_channel *chan, void *data)
00752 {
00753    int res = 0, digit = 3;
00754    struct ast_config *cfg, *ucfg;
00755    const char *dirintro;
00756    char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { 0, };
00757    struct ast_flags flags = { 0 };
00758    struct ast_flags config_flags = { 0 };
00759    enum { FIRST, LAST, BOTH } which = LAST;
00760    char digits[9] = "digits/3";
00761    AST_DECLARE_APP_ARGS(args,
00762       AST_APP_ARG(vmcontext);
00763       AST_APP_ARG(dialcontext);
00764       AST_APP_ARG(options);
00765    );
00766 
00767    parse = ast_strdupa(data);
00768 
00769    AST_STANDARD_APP_ARGS(args, parse);
00770 
00771    if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
00772       return -1;
00773 
00774    if (!(cfg = realtime_directory(args.vmcontext))) {
00775       ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
00776       return -1;
00777    }
00778 
00779    if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) {
00780       ast_log(LOG_ERROR, "Config file users.conf is in an invalid format.  Aborting.\n");
00781       ucfg = NULL;
00782    }
00783 
00784    dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
00785    if (ast_strlen_zero(dirintro))
00786       dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
00787    /* the above prompts probably should be modified to include 0 for dialing operator
00788       and # for exiting (continues in dialplan) */
00789 
00790    if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00791       if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
00792          digit = atoi(opts[OPT_ARG_EITHER]);
00793       }
00794       which = BOTH;
00795    } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00796       if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
00797          digit = atoi(opts[OPT_ARG_FIRSTNAME]);
00798       }
00799       which = FIRST;
00800    } else {
00801       if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
00802          digit = atoi(opts[OPT_ARG_LASTNAME]);
00803       }
00804       which = LAST;
00805    }
00806 
00807    /* If no options specified, search by last name */
00808    if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00809       ast_set_flag(&flags, OPT_LISTBYLASTNAME);
00810       which = LAST;
00811    }
00812 
00813    if (digit > 9) {
00814       digit = 9;
00815    } else if (digit < 1) {
00816       digit = 3;
00817    }
00818    digits[7] = digit + '0';
00819 
00820    if (chan->_state != AST_STATE_UP)
00821       res = ast_answer(chan);
00822 
00823    for (;;) {
00824       if (!ast_strlen_zero(dirintro) && !res) {
00825          res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
00826       } else if (!res) {
00827          /* Stop playing sounds as soon as we have a digit. */
00828          res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
00829          if (!res) {
00830             res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
00831          }
00832          if (!res) {
00833             res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
00834          }
00835          if (!res) {
00836             res = ast_stream_and_wait(chan, 
00837                which == FIRST ? "dir-first" :
00838                which == LAST ? "dir-last" :
00839                "dir-firstlast", AST_DIGIT_ANY);
00840          }
00841          if (!res) {
00842             res = ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
00843          }
00844       }
00845       ast_stopstream(chan);
00846       if (!res)
00847          res = ast_waitfordigit(chan, 5000);
00848 
00849       if (res <= 0)
00850          break;
00851 
00852       res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags, opts);
00853       if (res)
00854          break;
00855 
00856       res = ast_waitstream(chan, AST_DIGIT_ANY);
00857       ast_stopstream(chan);
00858 
00859       if (res)
00860          break;
00861    }
00862 
00863    if (ucfg)
00864       ast_config_destroy(ucfg);
00865    ast_config_destroy(cfg);
00866 
00867    return res < 0 ? -1 : 0;
00868 }
00869 
00870 static int unload_module(void)
00871 {
00872    int res;
00873    res = ast_unregister_application(app);
00874    return res;
00875 }
00876 
00877 static int load_module(void)
00878 {
00879    return ast_register_application_xml(app, directory_exec);
00880 }
00881 
00882 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");