From d2b596f1082874b76a8c3ceaf0851b26787d3be6 Mon Sep 17 00:00:00 2001 From: Martin Pycko Date: Fri, 19 Dec 2003 18:06:29 +0000 Subject: [PATCH] Add voicemail prepending feature plus forwarding to many extensions if you specify exten1*exten2*.....# git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@1872 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- apps/app_voicemail.c | 445 ++++++++++++++++++++++++++++------- file.c | 74 +++++- include/asterisk/file.h | 23 ++ sounds.txt | 2 + sounds/vm-forwardoptions.gsm | Bin 0 -> 9702 bytes 5 files changed, 456 insertions(+), 88 deletions(-) create mode 100755 sounds/vm-forwardoptions.gsm diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index a7b43a88d7..ced94c6393 100755 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -109,6 +109,23 @@ struct vm_zone { struct vm_zone *next; }; +struct vm_state { + char curbox[80]; + char username[80]; + char curdir[256]; + char vmbox[256]; + char fn[256]; + char fn2[256]; + int deleted[MAXMSG]; + int heard[MAXMSG]; + int curmsg; + int lastmsg; + int newmessages; + int oldmessages; + int starting; + int repeats; +}; + static char *tdesc = "Comedian Mail (Voicemail System)"; static char *adapp = "CoMa"; @@ -874,6 +891,214 @@ static int play_and_wait(struct ast_channel *chan, char *fn) return d; } +static int play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt) +{ + char d, *fmts; + char comment[256]; + int x, fmtcnt=1, res=-1,outmsg=0; + struct ast_frame *f; + struct ast_filestream *others[MAX_OTHER_FORMATS]; + struct ast_filestream *realfiles[MAX_OTHER_FORMATS]; + char *sfmt[MAX_OTHER_FORMATS]; + char *stringp=NULL; + time_t start, end; + struct ast_dsp *sildet; /* silence detector dsp */ + int totalsilence = 0; + int dspsilence = 0; + int gotsilence = 0; /* did we timeout for silence? */ + int rfmt=0; + char prependfile[80]; + + ast_log(LOG_DEBUG,"play_and_preped: %s, %s, '%s'\n", playfile ? playfile : "", recordfile, fmt); + snprintf(comment,sizeof(comment),"Playing %s, Recording to: %s on %s\n", playfile ? playfile : "", recordfile, chan->name); + + if (playfile) { + d = play_and_wait(chan, playfile); + if (d > -1) + d = ast_streamfile(chan, "beep",chan->language); + if (!d) + d = ast_waitstream(chan,""); + if (d < 0) + return -1; + } + strncpy(prependfile, recordfile, sizeof(prependfile) -1); + strcat(prependfile, "-prepend"); + + fmts = ast_strdupa(fmt); + + stringp=fmts; + strsep(&stringp, "|"); + ast_log(LOG_DEBUG,"Recording Formats: sfmts=%s\n", fmts); + sfmt[0] = ast_strdupa(fmts); + + while((fmt = strsep(&stringp, "|"))) { + if (fmtcnt > MAX_OTHER_FORMATS - 1) { + ast_log(LOG_WARNING, "Please increase MAX_OTHER_FORMATS in app_voicemail.c\n"); + break; + } + sfmt[fmtcnt++] = ast_strdupa(fmt); + } + + if (maxtime) + time(&start); + for (x=0;x 0) { + rfmt = chan->readformat; + res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n"); + return -1; + } + } + + if (x == fmtcnt) { + /* Loop forever, writing the packets we read to the writer(s), until + we read a # or get a hangup */ + f = NULL; + for(;;) { + res = ast_waitfor(chan, 2000); + if (!res) { + ast_log(LOG_DEBUG, "One waitfor failed, trying another\n"); + /* Try one more time in case of masq */ + res = ast_waitfor(chan, 2000); + if (!res) { + ast_log(LOG_WARNING, "No audio available on %s??\n", chan->name); + res = -1; + } + } + + if (res < 0) { + f = NULL; + break; + } + f = ast_read(chan); + if (!f) + break; + if (f->frametype == AST_FRAME_VOICE) { + /* write each format */ + for (x=0;x 0) { + dspsilence = 0; + ast_dsp_silence(sildet, f, &dspsilence); + if (dspsilence) + totalsilence = dspsilence; + else + totalsilence = 0; + + if (totalsilence > maxsilence) { + /* Ended happily with silence */ + ast_frfree(f); + gotsilence = 1; + outmsg=2; + break; + } + } + /* Exit on any error */ + if (res) { + ast_log(LOG_WARNING, "Error writing frame\n"); + ast_frfree(f); + break; + } + } else if (f->frametype == AST_FRAME_VIDEO) { + /* Write only once */ + ast_writestream(others[0], f); + } else if (f->frametype == AST_FRAME_DTMF) { + /* stop recording with any digit */ + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "User ended message by pressing %c\n", f->subclass); + res = f->subclass; + outmsg = 2; + ast_frfree(f); + break; + } + if (maxtime) { + time(&end); + if (maxtime < (end - start)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Took too long, cutting it short...\n"); + res = 't'; + ast_frfree(f); + break; + } + } + ast_frfree(f); + } + if (!f) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "User hung up\n"); + res = -1; + outmsg=1; + /* delete all the prepend files */ + for (x=0;x 1) { + struct ast_frame *fr; + for (x=0;xname); + } + } + if (outmsg) { + if (outmsg > 1) { + /* Let them know it worked */ + ast_streamfile(chan, "vm-msgsaved", chan->language); + ast_waitstream(chan, ""); + } + } + return res; +} + static int play_and_record(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt) { char d, *fmts; @@ -1891,8 +2116,45 @@ static int get_folder2(struct ast_channel *chan, char *fn, int start) return res; } -static int -forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender, char *fmt) +static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, char *curdir, int curmsg, char *vmfts, char *context) +{ + int cmd = 0; + int retries = 0; + + while((cmd >= 0) && (cmd != 't') && (cmd != '#')) { + if (cmd) + retries = 0; + switch (cmd) { + case '1': + /* prepend a message to the current message and return */ + { + char file[200]; + snprintf(file, sizeof(file), "%s/msg%04d", curdir, curmsg); + cmd = play_and_prepend(chan, NULL, file, 0, vmfmts); + break; + } + case '2': + cmd = 't'; + break; + case '#': + cmd = '#'; + break; + default: + cmd = play_and_wait(chan,"vm-forwardoptions"); + if (!cmd) + cmd = ast_waitfordigit(chan,6000); + if (!cmd) + retries++; + if (retries > 3) + cmd = 't'; + } + } + if (cmd == 't') + cmd = 0; + return cmd; +} + +static int forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender, char *fmt) { char username[70]; char sys[256]; @@ -1903,105 +2165,126 @@ forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, char miffile[256]; char fn[256]; char callerid[512]; - int res = 0; - struct ast_vm_user *receiver, srec; + int res = 0, cmd = 0; + struct ast_vm_user *receiver, *extensions = NULL, *vmtmp = NULL; char tmp[256]; char *stringp, *s; - - while(!res) { + int saved_messages = 0, found = 0; + int valid_extensions = 0; + while (!res && !valid_extensions) { res = ast_streamfile(chan, "vm-extension", chan->language); if (res) break; if ((res = ast_readstring(chan, username, sizeof(username) - 1, 2000, 10000, "#") < 0)) break; - if ((receiver = find_user(&srec, context, username))) { - /* if (play_and_wait(chan, "vm-savedto")) + /* start all over if no username */ + if (!strlen(username)) + continue; + stringp = username; + s = strsep(&stringp, "*"); + /* start optimistic */ + valid_extensions = 1; + while (s) { + /* find_user is going to malloc since we have a NULL as first argument */ + if ((receiver = find_user(NULL, context, s))) { + if (!extensions) + vmtmp = extensions = receiver; + else { + vmtmp->next = receiver; + vmtmp = receiver; + } + found++; + } else { + valid_extensions = 0; break; - */ - - snprintf(todir, sizeof(todir), "%s/voicemail/%s/%s/INBOX", (char *)ast_config_AST_SPOOL_DIR, receiver->context, username); - snprintf(sys, sizeof(sys), "mkdir -p %s\n", todir); - ast_log(LOG_DEBUG, sys); - system(sys); - - todircount = count_messages(todir); - strncpy(tmp, fmt, sizeof(tmp)); - stringp = tmp; - while((s = strsep(&stringp, "|"))) { - /* XXX This is a hack -- we should use build_filename or similar XXX */ - if (!strcasecmp(s, "wav49")) - s = "WAV"; - snprintf(sys, sizeof(sys), "cp %s/msg%04d.%s %s/msg%04d.%s\n", dir, curmsg, s, todir, todircount, s); - ast_log(LOG_DEBUG, sys); - system(sys); } - snprintf(sys, sizeof(sys), "cp %s/msg%04d.txt %s/msg%04d.txt\n", dir, curmsg, todir, todircount); + s = strsep(&stringp, "*"); + } + /* break from the loop of reading the extensions */ + if (valid_extensions) + break; + /* invalid extension, try again */ + res = play_and_wait(chan, "pbx-invalid"); + } + /* check if we're clear to proceed */ + if (!extensions || !valid_extensions) + return res; + vmtmp = extensions; + cmd = vm_forwardoptions(chan, sender, dir, curmsg, vmfmts, context); + + while(!res && vmtmp) { + /* if (play_and_wait(chan, "vm-savedto")) + break; + */ + snprintf(todir, sizeof(todir), "%s/voicemail/%s/%s/INBOX", (char *)ast_config_AST_SPOOL_DIR, vmtmp->context, vmtmp->mailbox); + snprintf(sys, sizeof(sys), "mkdir -p %s\n", todir); + ast_log(LOG_DEBUG, sys); + system(sys); + + todircount = count_messages(todir); + strncpy(tmp, fmt, sizeof(tmp)); + stringp = tmp; + while((s = strsep(&stringp, "|"))) { + /* XXX This is a hack -- we should use build_filename or similar XXX */ + if (!strcasecmp(s, "wav49")) + s = "WAV"; + snprintf(sys, sizeof(sys), "cp %s/msg%04d.%s %s/msg%04d.%s\n", dir, curmsg, s, todir, todircount, s); ast_log(LOG_DEBUG, sys); system(sys); - snprintf(fn, sizeof(fn), "%s/msg%04d", todir,todircount); + } + snprintf(sys, sizeof(sys), "cp %s/msg%04d.txt %s/msg%04d.txt\n", dir, curmsg, todir, todircount); + ast_log(LOG_DEBUG, sys); + system(sys); + snprintf(fn, sizeof(fn), "%s/msg%04d", todir,todircount); - /* load the information on the source message so we can send an e-mail like a new message */ - snprintf(miffile, sizeof(miffile), "%s/msg%04d.txt", dir, curmsg); - if ((mif=ast_load(miffile))) { + /* load the information on the source message so we can send an e-mail like a new message */ + snprintf(miffile, sizeof(miffile), "%s/msg%04d.txt", dir, curmsg); + if ((mif=ast_load(miffile))) { - /* set callerid and duration variables */ - snprintf(callerid, sizeof(callerid), "FWD from: %s from %s", sender->fullname, ast_variable_retrieve(mif, NULL, "callerid")); - s = ast_variable_retrieve(mif, NULL, "duration"); - if (s) - duration = atol(s); - else - duration = 0; - if (strlen(receiver->email)) { + /* set callerid and duration variables */ + snprintf(callerid, sizeof(callerid), "FWD from: %s from %s", sender->fullname, ast_variable_retrieve(mif, NULL, "callerid")); + s = ast_variable_retrieve(mif, NULL, "duration"); + if (s) + duration = atol(s); + else + duration = 0; + if (strlen(vmtmp->email)) { int attach_user_voicemail = attach_voicemail; char *myserveremail = serveremail; - if (receiver->attach > -1) - attach_user_voicemail = receiver->attach; - if (strlen(receiver->serveremail)) - myserveremail = receiver->serveremail; - sendmail(myserveremail, receiver, todircount, username, callerid, fn, tmp, duration, attach_user_voicemail); - } - - if (strlen(receiver->pager)) { + if (vmtmp->attach > -1) + attach_user_voicemail = vmtmp->attach; + if (strlen(vmtmp->serveremail)) + myserveremail = vmtmp->serveremail; + sendmail(myserveremail, vmtmp, todircount, vmtmp->mailbox, callerid, fn, tmp, duration, attach_user_voicemail); + } + + if (strlen(vmtmp->pager)) { char *myserveremail = serveremail; - if (strlen(receiver->serveremail)) - myserveremail = receiver->serveremail; - sendpage(myserveremail, receiver->pager, todircount, username, callerid, duration, receiver); + if (strlen(vmtmp->serveremail)) + myserveremail = vmtmp->serveremail; + sendpage(myserveremail, vmtmp->pager, todircount, vmtmp->mailbox, callerid, duration, vmtmp); } - ast_destroy(mif); /* or here */ - } - /* Leave voicemail for someone */ - manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", username, ast_app_has_voicemail(username)); - - /* give confirmatopm that the message was saved */ - res = play_and_wait(chan, "vm-message"); - if (!res) - res = play_and_wait(chan, "vm-saved"); - free_user(receiver); - break; - } else { - res = play_and_wait(chan, "pbx-invalid"); + ast_destroy(mif); /* or here */ } - } - return res; -} + /* Leave voicemail for someone */ + manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", vmtmp->mailbox, ast_app_has_voicemail(vmtmp->mailbox)); -struct vm_state { - char curbox[80]; - char username[80]; - char curdir[256]; - char vmbox[256]; - char fn[256]; - char fn2[256]; - int deleted[MAXMSG]; - int heard[MAXMSG]; - int curmsg; - int lastmsg; - int newmessages; - int oldmessages; - int starting; - int repeats; -}; + free_user(vmtmp); + saved_messages++; + vmtmp = vmtmp->next; + } + if (saved_messages > 0) { + /* give confirmatopm that the message was saved */ + if (saved_messages == 1) + res = play_and_wait(chan, "vm-message"); + else + res = play_and_wait(chan, "vm-messages"); + if (!res) + res = play_and_wait(chan, "vm-saved"); + } + return res ? res : cmd; +} static int wait_file2(struct ast_channel *chan, struct vm_state *vms, char *file) diff --git a/file.c b/file.c index 4213a23e2d..9869eb325f 100755 --- a/file.c +++ b/file.c @@ -499,6 +499,15 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, char *filename, return NULL; } +struct ast_frame *ast_readframe(struct ast_filestream *s) +{ + struct ast_frame *f = NULL; + int whennext = 0; + if (s && s->fmt) + f = s->fmt->read(s, &whennext); + return f; +} + static int ast_readaudio_callback(void *data) { struct ast_filestream *s = data; @@ -726,10 +735,9 @@ int ast_streamfile(struct ast_channel *chan, char *filename, char *preflang) return -1; } - -struct ast_filestream *ast_writefile(char *filename, char *type, char *comment, int flags, int check, mode_t mode) +struct ast_filestream *ast_readfile(char *filename, char *type, char *comment, int flags, int check, mode_t mode) { - int fd,myflags; + int fd,myflags = 0; struct ast_format *f; struct ast_filestream *fs=NULL; char *fn; @@ -738,9 +746,6 @@ struct ast_filestream *ast_writefile(char *filename, char *type, char *comment, ast_log(LOG_WARNING, "Unable to lock format list\n"); return NULL; } - myflags = 0; - /* set the O_TRUNC flag if and only if there is no O_APPEND specified */ - if (!(flags & O_APPEND)) myflags = O_TRUNC; f = formats; while(f) { if (!strcasecmp(f->name, type)) { @@ -750,7 +755,62 @@ struct ast_filestream *ast_writefile(char *filename, char *type, char *comment, stringp=ext; ext = strsep(&stringp, "|"); fn = build_filename(filename, ext); - fd = open(fn, flags | myflags | O_WRONLY | O_CREAT, mode); + fd = open(fn, flags | myflags); + if (fd >= 0) { + errno = 0; + if ((fs = f->open(fd))) { + fs->trans = NULL; + fs->fmt = f; + fs->flags = flags; + fs->mode = mode; + fs->filename = strdup(filename); + fs->vfs = NULL; + } else { + ast_log(LOG_WARNING, "Unable to open %s\n", fn); + close(fd); + unlink(fn); + } + } else if (errno != EEXIST) + ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno)); + free(fn); + free(ext); + break; + } + f = f->next; + } + ast_mutex_unlock(&formatlock); + if (!f) + ast_log(LOG_WARNING, "No such format '%s'\n", type); + return fs; +} + +struct ast_filestream *ast_writefile(char *filename, char *type, char *comment, int flags, int check, mode_t mode) +{ + int fd,myflags = 0; + struct ast_format *f; + struct ast_filestream *fs=NULL; + char *fn; + char *ext; + if (ast_mutex_lock(&formatlock)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return NULL; + } + /* set the O_TRUNC flag if and only if there is no O_APPEND specified */ + if (!(flags & O_APPEND)) + myflags = O_TRUNC; + + myflags |= O_WRONLY | O_CREAT; + + f = formats; + while(f) { + if (!strcasecmp(f->name, type)) { + char *stringp=NULL; + /* XXX Implement check XXX */ + ext = strdup(f->exts); + stringp=ext; + ext = strsep(&stringp, "|"); + fn = build_filename(filename, ext); + fd = open(fn, flags | myflags, mode); if (fd >= 0) { errno = 0; if ((fs = f->rewrite(fd, comment))) { diff --git a/include/asterisk/file.h b/include/asterisk/file.h index f7808e435c..316e471121 100755 --- a/include/asterisk/file.h +++ b/include/asterisk/file.h @@ -140,6 +140,22 @@ char ast_waitstream_fr(struct ast_channel *c, char *breakon, char *forward, char 1 if monfd is ready for reading */ char ast_waitstream_full(struct ast_channel *c, char *breakon, int audiofd, int monfd); +//! Starts reading from a file +/*! + * \param filename the name of the file to write to + * \param type format of file you wish to write out to + * \param comment comment to go with + * \param oflags output file flags + * \param check (unimplemented, hence negligible) + * \param mode Open mode + * Open an incoming file stream. oflags are flags for the open() command, and + * if check is non-zero, then it will not write a file if there are any files that + * start with that name and have an extension + * Please note, this is a blocking function. Program execution will not return until ast_waitstream completes it's execution. + * Returns a struct ast_filestream on success, NULL on failure + */ +struct ast_filestream *ast_readfile(char *filename, char *type, char *comment, int oflags, int check, mode_t mode); + //! Starts writing a file /*! * \param filename the name of the file to write to @@ -261,6 +277,13 @@ int ast_stream_rewind(struct ast_filestream *fs, long ms); */ long ast_tellstream(struct ast_filestream *fs); +//! Read a frame from a filestream +/*! + * \param ast_filestream fs to act on + * Returns a frame or NULL if read failed + */ +struct ast_frame *ast_readframe(struct ast_filestream *s); + #define AST_RESERVED_POINTERS 20 #if defined(__cplusplus) || defined(c_plusplus) diff --git a/sounds.txt b/sounds.txt index 86655bc478..71b5c16556 100755 --- a/sounds.txt +++ b/sounds.txt @@ -392,3 +392,5 @@ %minutes.gsm%minutes +%vm-forwardoptions.gsm%press 1 to prepend a message or 2 to forward the + message without prepending diff --git a/sounds/vm-forwardoptions.gsm b/sounds/vm-forwardoptions.gsm new file mode 100755 index 0000000000000000000000000000000000000000..b9ccb7f98002c9b899db20eac904530744db273a GIT binary patch literal 9702 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