Mon Jun 27 16:50:46 2011

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

Generated on Mon Jun 27 16:50:46 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7