Sat Mar 10 01:53:56 2012

Asterisk developer's documentation


app_directory.c

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

Generated on Sat Mar 10 01:53:56 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7