Add placeholder IVR application support (static version)
authorMark Spencer <markster@digium.com>
Thu, 17 Feb 2005 14:57:36 +0000 (14:57 +0000)
committerMark Spencer <markster@digium.com>
Thu, 17 Feb 2005 14:57:36 +0000 (14:57 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@5040 65c4cc65-6c06-0410-ace0-fbb531ad65f3

app.c
apps/Makefile
apps/app_ivrdemo.c [new file with mode: 0755]
include/asterisk/app.h

diff --git a/app.c b/app.c
index 8cc7af1..c1a0291 100755 (executable)
--- a/app.c
+++ b/app.c
@@ -1208,3 +1208,180 @@ int ast_record_review(struct ast_channel *chan, const char *playfile, const char
                cmd = 0;
        return cmd;
 }
+
+#define RES_UPONE (1 << 16)
+#define RES_EXIT  (1 << 17)
+#define RES_REPEAT (1 << 18)
+#define RES_RESTART ((1 << 19) | RES_REPEAT)
+
+static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata);
+static int ivr_dispatch(struct ast_channel *chan, struct ast_ivr_option *option, char *exten, void *cbdata)
+{
+       int res;
+       switch(option->action) {
+       case AST_ACTION_UPONE:
+               return RES_UPONE;
+       case AST_ACTION_EXIT:
+               return RES_EXIT | (((unsigned long)(option->adata)) & 0xffff);
+       case AST_ACTION_REPEAT:
+               return RES_REPEAT | (((unsigned long)(option->adata)) & 0xffff);
+       case AST_ACTION_RESTART:
+               return RES_RESTART ;
+       case AST_ACTION_NOOP:
+               return 0;
+       case AST_ACTION_BACKGROUND:
+               res = ast_streamfile(chan, (char *)option->adata, chan->language);
+               if (!res) {
+                       res = ast_waitstream(chan, AST_DIGIT_ANY);
+               } else {
+                       ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata);
+                       res = 0;
+               }
+               return res;
+       case AST_ACTION_PLAYBACK:
+               res = ast_streamfile(chan, (char *)option->adata, chan->language);
+               if (!res) {
+                       res = ast_waitstream(chan, "");
+               } else {
+                       ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata);
+                       res = 0;
+               }
+               return res;
+       case AST_ACTION_MENU:
+               res = ast_ivr_menu_run_internal(chan, (struct ast_ivr_menu *)option->adata, cbdata);
+               return res;
+       case AST_ACTION_CALLBACK:
+       case AST_ACTION_PLAYLIST:
+       case AST_ACTION_TRANSFER:
+       case AST_ACTION_WAITOPTION:
+               ast_log(LOG_NOTICE, "Unimplemented dispatch function %d, ignoring!\n", option->action);
+               return 0;
+       default:
+               ast_log(LOG_NOTICE, "Unknown dispatch function %d, ignoring!\n", option->action);
+               return 0;
+       };
+       return -1;
+}
+
+static int option_exists(struct ast_ivr_menu *menu, char *option)
+{
+       int x;
+       for (x=0;menu->options[x].option;x++)
+               if (!strcasecmp(menu->options[x].option, option))
+                       return x;
+       return -1;
+}
+
+static int option_matchmore(struct ast_ivr_menu *menu, char *option)
+{
+       int x;
+       for (x=0;menu->options[x].option;x++)
+               if ((!strncasecmp(menu->options[x].option, option, strlen(option))) && 
+                               (menu->options[x].option[strlen(option)]))
+                       return x;
+       return -1;
+}
+
+static int read_newoption(struct ast_channel *chan, struct ast_ivr_menu *menu, char *exten, int maxexten)
+{
+       int res=0;
+       int ms;
+       while(option_matchmore(menu, exten)) {
+               ms = chan->pbx ? chan->pbx->dtimeout : 5000;
+               if (strlen(exten) >= maxexten - 1) 
+                       break;
+               res = ast_waitfordigit(chan, ms);
+               if (res < 1)
+                       break;
+               exten[strlen(exten) + 1] = '\0';
+               exten[strlen(exten)] = res;
+       }
+       return res > 0 ? 0 : res;
+}
+
+static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata)
+{
+       /* Execute an IVR menu structure */
+       int res=0;
+       int pos = 0;
+       int retries = 0;
+       char exten[AST_MAX_EXTENSION] = "s";
+       if (option_exists(menu, "s") < 0) {
+               strcpy(exten, "g");
+               if (option_exists(menu, "g") < 0) {
+                       ast_log(LOG_WARNING, "No 's' nor 'g' extension in menu '%s'!\n", menu->title);
+                       return -1;
+               }
+       }
+       while(!res) {
+               while(menu->options[pos].option) {
+                       if (!strcasecmp(menu->options[pos].option, exten)) {
+                               res = ivr_dispatch(chan, menu->options + pos, exten, cbdata);
+                               if (res < 0)
+                                       break;
+                               else if (res & RES_UPONE)
+                                       return 0;
+                               else if (res & RES_EXIT)
+                                       return res;
+                               else if (res & RES_REPEAT) {
+                                       int maxretries = res & 0xffff;
+                                       if (res & RES_RESTART)
+                                               retries = 0;
+                                       else
+                                               retries++;
+                                       if (!maxretries)
+                                               maxretries = 3;
+                                       if ((maxretries > 0) && (retries >= maxretries))
+                                               return -2;
+                                       else {
+                                               if (option_exists(menu, "g") > -1) 
+                                                       strcpy(exten, "g");
+                                               else if (option_exists(menu, "s") > -1)
+                                                       strcpy(exten, "s");
+                                       }
+                                       pos=0;
+                               } else if (res && strchr(AST_DIGIT_ANY, res)) {
+                                       ast_log(LOG_DEBUG, "Got start of extension, %c\n", res);
+                                       exten[1] = '\0';
+                                       exten[0] = res;
+                                       if ((res = read_newoption(chan, menu, exten, sizeof(exten))))
+                                               break;
+                                       if (!option_exists(menu, exten)) {
+                                               if (option_exists(menu, "i")) {
+                                                       strcpy(exten, "i");
+                                                       pos = 0;
+                                                       continue;
+                                               } else {
+                                                       ast_log(LOG_DEBUG, "Aborting on invalid entry, with no 'i' option!\n");
+                                                       res = -2;
+                                                       break;
+                                               }
+                                       } else {
+                                               pos = 0;
+                                               continue;
+                                       }
+                               }
+                       }
+                       pos++;
+               }
+               ast_log(LOG_DEBUG, "Stopping option '%s', res is %d\n", exten, res);
+               pos = 0;
+               if (!strcasecmp(exten, "s"))
+                       strcpy(exten, "g");
+               else if (strcasecmp(exten, "t"))
+                       strcpy(exten, "t");
+               else
+                       break;
+       }
+       return res;
+}
+
+int ast_ivr_menu_run(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata)
+{
+       int res;
+       res = ast_ivr_menu_run_internal(chan, menu, cbdata);
+       /* Hide internal coding */
+       if (res > 0)
+               res = 0;
+       return res;
+}
index d66ff31..a8a4e3b 100755 (executable)
@@ -45,6 +45,10 @@ endif
 #APPS+=app_sql_postgres.so
 #APPS+=app_sql_odbc.so
 #APPS+=app_rpt.so
+#
+# Experimental things
+#
+#APPS+=app_ivrdemo.so
 
 APPS+=$(shell if [ -f /usr/include/linux/zaptel.h ]; then echo "app_zapras.so app_meetme.so app_flash.so app_zapbarge.so app_zapscan.so" ; fi)
 APPS+=$(shell if [ -f /usr/local/include/zaptel.h ]; then echo "app_zapras.so app_meetme.so app_flash.so app_zapbarge.so app_zapscan.so" ; fi)
diff --git a/apps/app_ivrdemo.c b/apps/app_ivrdemo.c
new file mode 100755 (executable)
index 0000000..744c675
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Skeleton application
+ * 
+ * Copyright (C) 1999, Mark Spencer
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/lock.h>
+#include <asterisk/app.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+static char *tdesc = "IVR Demo Application";
+static char *app = "IVRDemo";
+static char *synopsis = 
+"  This is a skeleton application that shows you the basic structure to create your\n"
+"own asterisk applications and demonstrates the IVR demo.\n";
+
+static int ivr_demo_func(struct ast_channel *chan, void *data)
+{
+       ast_verbose("IVR Demo, data is %s!\n", (char *)data);
+       return 0;
+}
+
+static struct ast_ivr_menu ivr_submenu = {
+       "IVR Demo Sub Menu", 0, {
+       { "s", AST_ACTION_BACKGROUND, "demo-abouttotry" },
+       { "1", AST_ACTION_PLAYBACK, "digits/1" },
+       { "1", AST_ACTION_PLAYBACK, "digits/1" },
+       { "1", AST_ACTION_RESTART },
+       { "2", AST_ACTION_PLAYLIST, "digits/2;digits/3" },
+       { "*", AST_ACTION_REPEAT },
+       { "#", AST_ACTION_UPONE  },
+       { NULL },
+}};
+
+static struct ast_ivr_menu ivr_demo = {
+       "IVR Demo Main Menu", 0, {
+       { "s", AST_ACTION_BACKGROUND, "demo-congrats" },
+       { "g", AST_ACTION_BACKGROUND, "demo-instruct" },
+       { "g", AST_ACTION_WAITOPTION },
+       { "1", AST_ACTION_PLAYBACK, "digits/1" },
+       { "1", AST_ACTION_RESTART },
+       { "2", AST_ACTION_MENU, &ivr_submenu },
+       { "2", AST_ACTION_RESTART },
+       { "i", AST_ACTION_PLAYBACK, "invalid" },
+       { "i", AST_ACTION_REPEAT, (void *)2 },
+       { "#", AST_ACTION_EXIT },
+       { NULL },
+}};
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static int skel_exec(struct ast_channel *chan, void *data)
+{
+       int res=0;
+       struct localuser *u;
+       if (!data) {
+               ast_log(LOG_WARNING, "skel requires an argument (filename)\n");
+               return -1;
+       }
+       LOCAL_USER_ADD(u);
+       /* Do our thing here */
+       if (chan->_state != AST_STATE_UP)
+               res = ast_answer(chan);
+       if (!res)
+               res = ast_ivr_menu_run(chan, &ivr_demo, data);
+       LOCAL_USER_REMOVE(u);
+       return res;
+}
+
+int unload_module(void)
+{
+       STANDARD_HANGUP_LOCALUSERS;
+       return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+       return ast_register_application(app, skel_exec, synopsis, tdesc);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+       STANDARD_USECOUNT(res);
+       return res;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
index 2761c1e..f94d753 100755 (executable)
 #if defined(__cplusplus) || defined(c_plusplus)
 extern "C" {
 #endif
+
+/* IVR stuff */
+
+/* Callback function for IVR, returns 0 on completion, -1 on hangup or digit if
+   interrupted */
+typedef int (*ast_ivr_callback)(struct ast_channel *chan, char *option, void *cbdata);
+
+typedef enum {
+       AST_ACTION_UPONE,               /* adata is unused */
+       AST_ACTION_EXIT,                /* adata is the return value for ast_ivr_menu_run if channel was not hungup */
+       AST_ACTION_CALLBACK,    /* adata is an ast_ivr_callback */
+       AST_ACTION_PLAYBACK,    /* adata is file to play */
+       AST_ACTION_BACKGROUND,  /* adata is file to play */
+       AST_ACTION_PLAYLIST,    /* adata is list of files, separated by ; to play */
+       AST_ACTION_MENU,                /* adata is a pointer to an ast_ivr_menu */
+       AST_ACTION_REPEAT,              /* adata is max # of repeats, cast to a pointer */
+       AST_ACTION_RESTART,             /* adata is like repeat, but resets repeats to 0 */
+       AST_ACTION_TRANSFER,    /* adata is a string with exten[@context] */
+       AST_ACTION_WAITOPTION,  /* adata is a timeout, or 0 for defaults */
+       AST_ACTION_NOOP,                /* adata is unused */
+} ast_ivr_action;
+
+struct ast_ivr_option {
+       char *option;
+       ast_ivr_action action;
+       void *adata;    
+};
+
+/* 
+    Special "options" are: 
+   "s" - "start here (one time greeting)"
+   "g" - "greeting/instructions"
+   "t" - "timeout"
+   "h" - "hangup"
+   "i" - "invalid selection"
+
+*/
+
+struct ast_ivr_menu {
+       char *title;            /* Title of menu */
+       unsigned int flags;     /* Flags */
+       struct ast_ivr_option options[];        /* All options */
+};
+
+/*! Runs an IVR menu, returns 0 on successful completion, -1 on hangup, or -2 on user error in menu */
+extern int ast_ivr_menu_run(struct ast_channel *c, struct ast_ivr_menu *menu, void *cbdata);
+
 /*! Plays a stream and gets DTMF data from a channel */
 /*!
  * \param c Which channel one is interacting with