aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xapps/app_externalivr.c152
-rwxr-xr-xdoc/README.externalivr21
2 files changed, 118 insertions, 55 deletions
diff --git a/apps/app_externalivr.c b/apps/app_externalivr.c
index 17894694f..a80ee6888 100755
--- a/apps/app_externalivr.c
+++ b/apps/app_externalivr.c
@@ -32,13 +32,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
-static char *tdesc = "External IVR Interface Application";
+static const char *tdesc = "External IVR Interface Application";
-static char *app = "ExternalIVR";
+static const char *app = "ExternalIVR";
-static char *synopsis = "Interfaces with an external IVR application";
+static const char *synopsis = "Interfaces with an external IVR application";
-static char *descrip =
+static const char *descrip =
" ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
"and starts a generator on the channel. The generator's play list is\n"
"controlled by the external application, which can add and clear entries\n"
@@ -48,6 +48,12 @@ static char *descrip =
"when the channel is hung up.\n"
"See doc/README.externalivr for a protocol specification.\n";
+#define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel, ## __VA_ARGS__)
+
+#define send_child_event(fd, event) fprintf(fd, "%c,%10ld\n", event, time(NULL))
+
+#define send_child_event_data(fd, event, data) fprintf(fd, "%c,%10ld,%s\n", event, time(NULL), data)
+
struct playlist_entry {
AST_LIST_ENTRY(playlist_entry) list;
char filename[1];
@@ -57,7 +63,10 @@ struct localuser {
struct ast_channel *chan;
struct localuser *next;
AST_LIST_HEAD(playlist, playlist_entry) playlist;
+ AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
int list_cleared;
+ int playing_silence;
+ int option_autoclear;
};
LOCAL_USER_DECL;
@@ -65,8 +74,8 @@ LOCAL_USER_DECL;
struct gen_state {
struct localuser *u;
struct ast_filestream *stream;
+ struct playlist_entry *current;
int sample_queue;
- int playing_silence;
};
static void *gen_alloc(struct ast_channel *chan, void *params)
@@ -109,30 +118,36 @@ static int gen_nextfile(struct gen_state *state)
struct localuser *u = state->u;
char *file_to_stream;
- state->u->list_cleared = 0;
- state->playing_silence = 0;
+ u->list_cleared = 0;
+ u->playing_silence = 0;
gen_closestream(state);
+ if (state->current) {
+ if (!u->playing_silence) {
+ AST_LIST_LOCK(&u->finishlist);
+ AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
+ AST_LIST_UNLOCK(&u->finishlist);
+ }
+ state->current = NULL;
+ }
while (!state->stream) {
- if (AST_LIST_FIRST(&u->playlist))
- entry = AST_LIST_REMOVE_HEAD(&u->playlist, list);
- else
- entry = NULL;
-
+ entry = AST_LIST_REMOVE_HEAD(&u->playlist, list);
if (entry) {
- file_to_stream = ast_strdupa(entry->filename);
- free(entry);
+ file_to_stream = entry->filename;
} else {
file_to_stream = "silence-10";
- state->playing_silence = 1;
+ u->playing_silence = 1;
}
+ state->current = entry;
+
if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
- ast_log(LOG_WARNING, "File '%s' could not be opened for channel '%s': %s\n", file_to_stream, u->chan->name, strerror(errno));
- if (!state->playing_silence)
+ ast_chan_log(LOG_WARNING, u->chan->name, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
+ if (!u->playing_silence) {
continue;
- else
+ } else {
break;
+ }
}
}
@@ -142,13 +157,14 @@ static int gen_nextfile(struct gen_state *state)
static struct ast_frame *gen_readframe(struct gen_state *state)
{
struct ast_frame *f = NULL;
+ struct localuser *u = state->u;
- if (state->u->list_cleared ||
- (state->playing_silence && AST_LIST_FIRST(&state->u->playlist))) {
+ if (u->list_cleared ||
+ (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
gen_closestream(state);
- AST_LIST_LOCK(&state->u->playlist);
+ AST_LIST_LOCK(&u->playlist);
gen_nextfile(state);
- AST_LIST_UNLOCK(&state->u->playlist);
+ AST_LIST_UNLOCK(&u->playlist);
}
if (!(state->stream && (f = ast_readframe(state->stream)))) {
@@ -174,7 +190,7 @@ static int gen_generate(struct ast_channel *chan, void *data, int len, int sampl
res = ast_write(chan, f);
ast_frfree(f);
if (res < 0) {
- ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
+ ast_chan_log(LOG_WARNING, chan->name, "Failed to write frame: %s\n", strerror(errno));
return -1;
}
state->sample_queue -= f->samples;
@@ -237,29 +253,30 @@ static int app_exec(struct ast_channel *chan, void *data)
LOCAL_USER_ADD(u);
if (pipe(child_stdin)) {
- ast_log(LOG_WARNING, "Could not create pipe for child input on channel '%s': %s\n", chan->name, strerror(errno));
+ ast_chan_log(LOG_WARNING, chan->name, "Could not create pipe for child input: %s\n", strerror(errno));
goto exit;
}
if (pipe(child_stdout)) {
- ast_log(LOG_WARNING, "Could not create pipe for child output on channel '%s': %s\n", chan->name, strerror(errno));
+ ast_chan_log(LOG_WARNING, chan->name, "Could not create pipe for child output: %s\n", strerror(errno));
goto exit;
}
if (pipe(child_stderr)) {
- ast_log(LOG_WARNING, "Could not create pipe for child errors on channel '%s': %s\n", chan->name, strerror(errno));
+ ast_chan_log(LOG_WARNING, chan->name, "Could not create pipe for child errors: %s\n", strerror(errno));
goto exit;
}
u->list_cleared = 0;
AST_LIST_HEAD_INIT(&u->playlist);
+ AST_LIST_HEAD_INIT(&u->finishlist);
if (chan->_state != AST_STATE_UP) {
ast_answer(chan);
}
if (ast_activate_generator(chan, &gen, u) < 0) {
- ast_log(LOG_WARNING,"Failed to activate generator on '%s'\n", chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Failed to activate generator\n");
goto exit;
} else
gen_active = 1;
@@ -302,19 +319,19 @@ static int app_exec(struct ast_channel *chan, void *data)
close(child_stderr[1]);
if (!(child_events = fdopen(child_events_fd, "w"))) {
- ast_log(LOG_WARNING, "Could not open stream for child events for channel '%s'\n", chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Could not open stream for child events\n");
goto exit;
}
setvbuf(child_events, NULL, _IONBF, 0);
if (!(child_commands = fdopen(child_commands_fd, "r"))) {
- ast_log(LOG_WARNING, "Could not open stream for child commands for channel '%s'\n", chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Could not open stream for child commands\n");
goto exit;
}
if (!(child_errors = fdopen(child_errors_fd, "r"))) {
- ast_log(LOG_WARNING, "Could not open stream for child errors for channel '%s'\n", chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Could not open stream for child errors\n");
goto exit;
}
@@ -322,40 +339,61 @@ static int app_exec(struct ast_channel *chan, void *data)
while (1) {
if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
- ast_log(LOG_NOTICE, "Channel '%s' is a zombie\n", chan->name);
+ ast_chan_log(LOG_NOTICE, chan->name, "Is a zombie\n");
res = -1;
break;
}
if (ast_check_hangup(chan)) {
- ast_log(LOG_NOTICE, "Channel '%s' got check_hangup\n", chan->name);
- fprintf(child_events, "H,%10ld\n", time(NULL));
+ ast_chan_log(LOG_NOTICE, chan->name, "Got check_hangup\n");
+ send_child_event(child_events, 'H');
res = -1;
break;
}
ready_fd = 0;
- ms = 1000;
+ ms = 100;
errno = 0;
exception = 0;
rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
+ if (!AST_LIST_EMPTY(&u->finishlist)) {
+ AST_LIST_LOCK(&u->finishlist);
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
+ send_child_event_data(child_events, 'F', entry->filename);
+ free(entry);
+ }
+ AST_LIST_UNLOCK(&u->finishlist);
+ }
+
if (rchan) {
/* the channel has something */
f = ast_read(chan);
if (!f) {
- fprintf(child_events, "H,%10ld\n", time(NULL));
- ast_log(LOG_NOTICE, "Channel '%s' returned no frame\n", chan->name);
+ ast_chan_log(LOG_NOTICE, chan->name, "Returned no frame\n");
+ send_child_event(child_events, 'H');
res = -1;
break;
}
if (f->frametype == AST_FRAME_DTMF) {
- fprintf(child_events, "%c,%10ld\n", f->subclass, time(NULL));
+ send_child_event(child_events, f->subclass);
+ if (u->option_autoclear) {
+ if (!u->playing_silence)
+ send_child_event(child_events, 'T');
+ AST_LIST_LOCK(&u->playlist);
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
+ if (!u->playing_silence)
+ send_child_event_data(child_events, 'D', entry->filename);
+ free(entry);
+ }
+ u->list_cleared = 1;
+ AST_LIST_UNLOCK(&u->playlist);
+ }
} else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
- ast_log(LOG_NOTICE, "Channel '%s' got AST_CONTROL_HANGUP\n", chan->name);
- fprintf(child_events, "H,%10ld\n", time(NULL));
+ ast_chan_log(LOG_NOTICE, chan->name, "Got AST_CONTROL_HANGUP\n");
+ send_child_event(child_events, 'H');
ast_frfree(f);
res = -1;
break;
@@ -365,7 +403,7 @@ static int app_exec(struct ast_channel *chan, void *data)
char input[1024];
if (exception || feof(child_commands)) {
- ast_log(LOG_WARNING, "Child process went away for channel '%s'\n", chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Child process went away\n");
res = -1;
break;
}
@@ -380,13 +418,18 @@ static int app_exec(struct ast_channel *chan, void *data)
if (input[0] == 'S') {
if (ast_fileexists(&input[2], NULL, NULL) == -1) {
- fprintf(child_events, "Z,%10ld\n", time(NULL));
- ast_log(LOG_WARNING, "Unknown file requested '%s' for channel '%s'\n", &input[2], chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Unknown file requested '%s'\n", &input[2]);
+ send_child_event(child_events, 'Z');
strcpy(&input[2], "exception");
}
+ if (!u->playing_silence)
+ send_child_event(child_events, 'T');
AST_LIST_LOCK(&u->playlist);
- while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
+ if (!u->playing_silence)
+ send_child_event_data(child_events, 'D', entry->filename);
free(entry);
+ }
u->list_cleared = 1;
entry = make_entry(&input[2]);
if (entry)
@@ -394,8 +437,8 @@ static int app_exec(struct ast_channel *chan, void *data)
AST_LIST_UNLOCK(&u->playlist);
} else if (input[0] == 'A') {
if (ast_fileexists(&input[2], NULL, NULL) == -1) {
- fprintf(child_events, "Z,%10ld\n", time(NULL));
- ast_log(LOG_WARNING, "Unknown file requested '%s' for channel '%s'\n", &input[2], chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Unknown file requested '%s'\n", &input[2]);
+ send_child_event(child_events, 'Z');
strcpy(&input[2], "exception");
}
entry = make_entry(&input[2]);
@@ -405,28 +448,35 @@ static int app_exec(struct ast_channel *chan, void *data)
AST_LIST_UNLOCK(&u->playlist);
}
} else if (input[0] == 'H') {
- ast_log(LOG_NOTICE, "Hanging up: %s\n", &input[2]);
- fprintf(child_events, "H,%10ld\n", time(NULL));
+ ast_chan_log(LOG_NOTICE, chan->name, "Hanging up: %s\n", &input[2]);
+ send_child_event(child_events, 'H');
break;
+ } else if (input[0] == 'O') {
+ if (!strcasecmp(&input[2], "autoclear"))
+ u->option_autoclear = 1;
+ else if (!strcasecmp(&input[2], "noautoclear"))
+ u->option_autoclear = 0;
+ else
+ ast_chan_log(LOG_WARNING, chan->name, "Unknown option requested '%s'\n", &input[2]);
}
} else if (ready_fd == child_errors_fd) {
char input[1024];
if (exception || feof(child_errors)) {
- ast_log(LOG_WARNING, "Child process went away for channel '%s'\n", chan->name);
+ ast_chan_log(LOG_WARNING, chan->name, "Child process went away\n");
res = -1;
break;
}
if (fgets(input, sizeof(input), child_errors)) {
command = ast_strip(input);
- ast_log(LOG_NOTICE, "%s\n", command);
+ ast_chan_log(LOG_NOTICE, chan->name, "stderr: %s\n", command);
}
} else if ((ready_fd < 0) && ms) {
if (errno == 0 || errno == EINTR)
continue;
- ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno));
+ ast_chan_log(LOG_WARNING, chan->name, "Wait failed (%s)\n", strerror(errno));
break;
}
}
@@ -484,7 +534,7 @@ int load_module(void)
char *description(void)
{
- return tdesc;
+ return (char *) tdesc;
}
int usecount(void)
diff --git a/doc/README.externalivr b/doc/README.externalivr
index ff4ff12c7..a1d4757e7 100755
--- a/doc/README.externalivr
+++ b/doc/README.externalivr
@@ -46,7 +46,7 @@ All events will be newline-terminated strings.
Events send to the child's stdin will be in the following format:
-tag,timestamp
+tag,timestamp[,data]
The tag can be one of the following characters:
@@ -57,6 +57,11 @@ A-D: DTMF event for keys A through D
H: the channel was hung up by the connected party
Z: the previous command was unable to be executed (file does not
exist, etc.)
+T: the play list was interrupted (see below)
+D: a file was dropped from the play list due to interruption (the
+data element will be the dropped file name)
+F: a file has finished playing (the data element will be the file
+name)
The timestamp will be 10 digits long, and will be a decimal
representation of a standard Unix epoch-based timestamp.
@@ -71,13 +76,17 @@ The child process can send commands on stdout in the following formats:
S,filename
A,filename
H,message
+O,option
The 'S' command checks to see if there is a playable audio file with
the specified name, and if so, clear's the generator's playlist and
places the file onto the list. Note that the playability check does
not take into account transcoding requirements, so it is possible for
the file to not be played even though it was found. If the file cannot
-be found, a 'Z' event (see above) will be sent to the child.
+be found, a 'Z' event (see above) will be sent to the child. If the
+generator is not currently playing silence, then T and D events will
+be sent to the child to signal the playlist interruption and notify
+it of the files that will not be played.
The 'A' command checks to see if there is a playable audio file with
the specified name, and if so, adds it to the generator's
@@ -87,10 +96,14 @@ playlist. The same playability and exception rules apply as for the
The 'H' command stops the generator and hangs up the channel, and logs
the supplied message to the Asterisk log.
+The 'O' command allows the child to set/clear options in the
+ExternalIVR() application. The supported options are:
+ autoclear/noautoclear:
+ Automatically interrupt and clear the playlist upon reception
+ of DTMF input.
+
Errors
------
Any newline-terminated output generated by the child process on its
stderr handle will be copied into the Asterisk log.
-
-