From d2b596f1082874b76a8c3ceaf0851b26787d3be6 Mon Sep 17 00:00:00 2001 From: Martin Pycko <martinp@digium.com> 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 | 443 ++++++++++++++++++++++++++++------- file.c | 68 +++++- include/asterisk/file.h | 23 ++ sounds.txt | 2 + sounds/vm-forwardoptions.gsm | Bin 0 -> 9702 bytes 5 files changed, 452 insertions(+), 84 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 : "<None>", recordfile, fmt); + snprintf(comment,sizeof(comment),"Playing %s, Recording to: %s on %s\n", playfile ? playfile : "<None>", 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<fmtcnt;x++) { + others[x] = ast_writefile(prependfile, sfmt[x], comment, O_TRUNC, 0, 0700); + ast_verbose( VERBOSE_PREFIX_3 "x=%i, open writing: %s format: %s, %p\n", x, prependfile, sfmt[x], others[x]); + if (!others[x]) { + break; + } + } + + sildet = ast_dsp_new(); //Create the silence detector + if (!sildet) { + ast_log(LOG_WARNING, "Unable to create silence detector :(\n"); + return -1; + } + ast_dsp_set_threshold(sildet, silencethreshold); + + if (maxsilence > 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<fmtcnt;x++) { + if (!others[x]) + break; + res = ast_writestream(others[x], f); + } + + /* Silence Detection */ + if (maxsilence > 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<fmtcnt;x++) { + if (!others[x]) + break; + ast_closestream(others[x]); + ast_filedelete(prependfile, sfmt[x]); + } + } + } else { + ast_log(LOG_WARNING, "Error creating writestream '%s', format '%s'\n", prependfile, sfmt[x]); + } + if (outmsg > 1) { + struct ast_frame *fr; + for (x=0;x<fmtcnt;x++) { + snprintf(comment, sizeof(comment), "Opening the real file %s.%s\n", recordfile, sfmt[x]); + realfiles[x] = ast_writefile(recordfile, sfmt[x], comment, O_RDONLY, 0, 0); + if (!others[x] || !realfiles[x]) + break; + if (totalsilence) + ast_stream_rewind(others[x], totalsilence-200); + else + ast_stream_rewind(others[x], 200); + ast_truncstream(others[x]); + /* add the original file too */ + while ((fr = ast_readframe(realfiles[x]))) { + ast_writestream(others[x],fr); + } + ast_closestream(others[x]); + ast_closestream(realfiles[x]); + ast_filerename(prependfile, recordfile, sfmt[x]); +#if 0 + ast_verbose("Recording Format: sfmts=%s, prependfile %s, recordfile %s\n", sfmt[x],prependfile,recordfile); +#endif + ast_filedelete(prependfile, sfmt[x]); + } + } + if (rfmt) { + if (ast_set_read_format(chan, rfmt)) { + ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name); + } + } + 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,106 +2165,127 @@ 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; - */ + } + 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); - 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); + 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(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); - 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); - 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))) { - - /* 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)) { + /* 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(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)); + ast_destroy(mif); /* or here */ + } + /* 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)); - /* give confirmatopm that the message was saved */ + 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"); - if (!res) - res = play_and_wait(chan, "vm-saved"); - free_user(receiver); - break; - } else { - res = play_and_wait(chan, "pbx-invalid"); - } + else + res = play_and_wait(chan, "vm-messages"); + if (!res) + res = play_and_wait(chan, "vm-saved"); } - return res; + return res ? res : cmd; } -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 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,58 @@ int ast_streamfile(struct ast_channel *chan, char *filename, char *preflang) return -1; } +struct ast_filestream *ast_readfile(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; + } + 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); + 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; + int fd,myflags = 0; struct ast_format *f; struct ast_filestream *fs=NULL; char *fn; @@ -738,9 +795,12 @@ 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; + if (!(flags & O_APPEND)) + myflags = O_TRUNC; + + myflags |= O_WRONLY | O_CREAT; + f = formats; while(f) { if (!strcasecmp(f->name, type)) { @@ -750,7 +810,7 @@ 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, 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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 P3fs^v_WD1d{2%;(Plke3 literal 0 HcmV?d00001 -- GitLab