Fri Nov 12 11:45:20 2010

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

Generated by  doxygen 1.6.2