diff -u apps/app_voicemail.c apps/app_voicemail.c --- apps/app_voicemail.c (working copy) +++ apps/app_voicemail.c (working copy) @@ -288,11 +288,107 @@ int (*has_voicemail)(const char *ext, const char *folder); }; +/* Message abstraction */ + +/* flags */ +#define VM_MSG_FILE_RETRIEVED 0x01 /*!< file has been retrieved from storage */ +#define VM_MSG_IS_NEW 0x02 /*!< message is newly created */ + +/*! Message handle. This should be a mostly opaque type; direct + access to its members is strongly discouraged except from within the + ast_vm_message_* functions. + + Instances of this structure shouldn't stay around for long; the user + object stored in them is owned by the calling code (for now), and the + directory where the voicemail is stored is locked for the lifetime of + this object. These objects should be instantiated, used, and then + immediately released. + */ + +struct ast_vm_message +{ + char context[256]; /*!< The context of this voicemail box */ + char ext[256]; /*!< The extension who this voicemail is for */ + char dir[256]; /*!< The directory where the voicemail files are stored */ + char fn[256]; /*!< The base filename (without .txt, .gsm, + etc.) for the voicemail files. Each voicemail is + stored in several files, which are formed by + adding various extensions to this member. */ + char fmt[80]; /*!< A |-separated list of the formats this voicemail is stored in */ + int msgnum; /*!< The index number of this message within its folder */ + struct ast_vm_user *user; /*!< The user structure this voicemail is for */ + int flags; /*!< Flags; see VM_MSG_* above */ + + struct ast_channel *chan; /*!< HACK: The channel associated with a new message */ + int duration; /*!< HACK: The duration of a new message, once it's known */ +}; + +/*! Create a new voicemail message. Only used when an actual new voicemail + is being recorded. + + \param vmu The user for whom the voicemail's being left. Do not free this + while the message handle still exists. + \param chan The channel from which the voicemail's being recorded + \param fmt A |-separated list of formats in which the message should be stored + \return A new message handle; call ast_vm_message_release() on it when you're done. + */ +static struct ast_vm_message *ast_vm_message_create(struct ast_vm_user *vmu, struct ast_channel *chan, const char *fmt); + +/*! Create a new voicemail message from another. + + Warning: Using this to make another copy of a message within the same folder + will cause a deadlock. + + \param src The message to copy + \param recip The user to send the copy of the voicemail to + \param chan The channel from which the original voicemail was recorded (for + caller ID data) + \param fmt A |-separated list of formats in which the message should be stored +*/ +static struct ast_vm_message *ast_vm_message_create_copy(struct ast_vm_message *src, struct ast_vm_user *recip, struct ast_channel *chan, char *fmt); + +/*! Release a voicemail message handle, unlocking the directory and freeing + the in-memory storage. + + \param message The message to release +*/ +static void ast_vm_message_release(struct ast_vm_message *message); + +/*! Delete a voicemail message, and release the handle which was being used + to access the message. + + Currently this only works properly for canceling new messages. + + \param message The message to delete +*/ +static void ast_vm_message_delete_and_release(struct ast_vm_message *message); + +/*! Get the sound file associated with a message. + + \param message The message to get the data for + \param filename A buffer to store the filename in + \param filenamelen The size of the buffer + \param fmt The format desired. NULL means any format. "" means + to return the raw filename (before appending the + format extension). + \return 0 on success, nonzero on failure +*/ +static int ast_vm_message_get_data(struct ast_vm_message *message, char *filename, int filenamelen, char *fmt); + +/*! Write metadata associated with a message. + + \param message The message to write metadata for + \param fmt printf arguments to print to the metadata file. +*/ +static void ast_vm_message_printf_metadata(struct ast_vm_message *message, const char *fmt, ...); + +/* End message abstraction */ + static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain); static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context); static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, - char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir, + char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, signed char record_gain); static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain); static int vm_play_folder_name(struct ast_channel *chan, char *mbox); @@ -2292,42 +2388,6 @@ return 0; } -static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname); - -static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt) -{ - char fromdir[256], todir[256], frompath[256], topath[256]; - char *frombox = mbox(imbox); - int recipmsgnum; - - ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->ext, vmu->context, recip->ext, recip->context); - - create_dirpath(todir, sizeof(todir), recip->context, recip->ext, "INBOX"); - - make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->ext, frombox); - make_file(frompath, sizeof(frompath), fromdir, msgnum); - - if (vm_lock_path(todir)) - return ERROR_LOCK_PATH; - - recipmsgnum = 0; - do { - make_file(topath, sizeof(topath), todir, recipmsgnum); - if (!storage->message_exists(todir, recipmsgnum, topath, chan->language)) - break; - recipmsgnum++; - } while (recipmsgnum < recip->maxmsg); - if (recipmsgnum < recip->maxmsg) { - storage->copy_file(fromdir, msgnum, todir, recipmsgnum, recip->ext, recip->context, frompath, topath); - } else { - ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->ext, recip->context); - } - ast_unlock_path(todir); - notify_new_message(chan, recip, recipmsgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name); - - return 0; -} - static void run_externnotify(char *context, char *extension) { char arguments[255]; @@ -2381,16 +2441,11 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options) { - char txtfile[256]; char callerid[256]; - FILE *txt; int res = 0; - int msgnum; - int duration = 0; int ausemacro = 0; int ousemacro = 0; char date[256]; - char dir[256]; char fn[256]; char prefile[256]=""; char tempfile[256]=""; @@ -2402,6 +2457,8 @@ struct ast_vm_user *vmu; struct ast_vm_user svm; const char *category = NULL; + struct ast_vm_message *message = NULL; + char duration_string[256]; ast_copy_string(tmp, ext, sizeof(tmp)); ext = tmp; @@ -2443,8 +2500,6 @@ if (ast_fileexists(tempfile, NULL, NULL) > 0) ast_copy_string(prefile, tempfile, sizeof(prefile)); storage->dispose_file(tempfile, -1); - /* It's easier just to try to make it than to check for its existence */ - create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX"); /* Check current or macro-calling context for special extensions */ if (!ast_strlen_zero(vmu->exit)) { @@ -2543,28 +2598,17 @@ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED"); return -1; } - /* The meat of recording the message... All the announcements and beeps have been played*/ + /* The meat of recording the message... All the announcements have been played, but not the beep */ ast_copy_string(fmt, vmfmts, sizeof(fmt)); if (!ast_strlen_zero(fmt)) { - msgnum = 0; - - if (vm_lock_path(dir)) { - free_user(vmu); - return ERROR_LOCK_PATH; - } - /* * This operation can be very expensive if done say over NFS or if the extension has 100+ messages * in the folder. So we should get this first so we don't cut off the first few seconds of the * message. */ - do { - make_file(fn, sizeof(fn), dir, msgnum); - if (!storage->message_exists(dir,msgnum,fn,chan->language)) - break; - msgnum++; - } while (msgnum < vmu->maxmsg); + message = ast_vm_message_create(vmu, chan, fmt); + /* Now play the beep once we have the message number for our next message. */ if (res >= 0) { /* Unless we're *really* silent, try to send the beep */ @@ -2572,16 +2616,24 @@ if (!res) res = ast_waitstream(chan, ""); } - if (msgnum < vmu->maxmsg) { + if (message != NULL) { + get_date(date, sizeof(date)); + + ast_vm_message_get_data(message, fn, sizeof(fn), ""); + /* assign a variable with the name of the voicemail file */ pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn); + /* duration is a HACK */ + res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &(message->duration), options->record_gain); + + if (res != '0') + snprintf(duration_string, sizeof(duration_string), "duration=%d\n", message->duration); + else + duration_string[0] = '0'; + /* Store information */ - snprintf(txtfile, sizeof(txtfile), "%s.txt", fn); - txt = fopen(txtfile, "w+"); - if (txt) { - get_date(date, sizeof(date)); - fprintf(txt, + ast_vm_message_printf_metadata(message, ";\n" "; Message Information file\n" ";\n" @@ -2595,7 +2647,8 @@ "callerid=%s\n" "origdate=%s\n" "origtime=%ld\n" - "category=%s\n", + "category=%s\n" + "%s", ext, chan->context, chan->macrocontext, @@ -2604,26 +2657,19 @@ chan->name, ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"), date, (long)time(NULL), - category ? category : ""); - } else - ast_log(LOG_WARNING, "Error opening text file for output\n"); - res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &duration, dir, options->record_gain); - if (res == '0') { - if (txt) - fclose(txt); + category ? category : "", + duration_string); + + if (res == '0') goto transfer; - } if (res > 0) res = 0; - if (txt) { - fprintf(txt, "duration=%d\n", duration); - fclose(txt); - } - if (duration < vmminmessage) { + if (message->duration < vmminmessage) { if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage); - storage->delete_file(dir,msgnum,fn); + ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", message->duration, vmminmessage); + ast_vm_message_delete_and_release(message); + /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED"); goto leave_vm_out; @@ -2640,18 +2686,20 @@ context++; } if ((recip = find_user(&recipu, context, exten))) { - copy_message(chan, vmu, 0, msgnum, duration, recip, fmt); + struct ast_vm_message *copy; + copy = ast_vm_message_create_copy(message, recip, chan, fmt); + if (copy) + ast_vm_message_release(copy); + else + ast_log(LOG_WARNING, "Couldn't copy message to %s\n", recip->ext); free_user(recip); } } - if (ast_fileexists(fn, NULL, NULL)) { - storage->store_file(dir, vmu->ext, vmu->context, msgnum); - notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name); - storage->dispose_file(dir, msgnum); - } + ast_vm_message_release(message); pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS"); } else { - ast_unlock_path(dir); + /* FIXME -- we assume that any problem is a full mailbox, which isn't always true */ + res = ast_streamfile(chan, "vm-mailboxfull", chan->language); if (!res) res = ast_waitstream(chan, ""); @@ -4649,7 +4697,7 @@ /* If forcename is set, have the user record their name */ if (ast_test_flag(vmu, VM_FORCENAME)) { snprintf(prefile,sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); if (cmd < 0 || cmd == 't' || cmd == '#') return cmd; } @@ -4657,11 +4705,11 @@ /* If forcegreetings is set, have the user record their greetings */ if (ast_test_flag(vmu, VM_FORCEGREET)) { snprintf(prefile,sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); if (cmd < 0 || cmd == 't' || cmd == '#') return cmd; snprintf(prefile,sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); if (cmd < 0 || cmd == 't' || cmd == '#') return cmd; } @@ -4695,15 +4743,15 @@ switch (cmd) { case '1': snprintf(prefile,sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); break; case '2': snprintf(prefile,sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); break; case '3': snprintf(prefile,sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); break; case '4': cmd = vm_tempgreeting(chan, vmu, vms, fmtc, record_gain); @@ -4789,12 +4837,12 @@ retries = 0; storage->retrieve_file(prefile, -1); if (ast_fileexists(prefile, NULL, NULL) <= 0) { - play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); cmd = 't'; } else { switch (cmd) { case '1': - cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain); + cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, record_gain); break; case '2': storage->delete_file(prefile, -1, prefile); @@ -6646,7 +6694,7 @@ } static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, - int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir, + int outsidecaller, struct ast_vm_user *vmu, int *duration, signed char record_gain) { /* Record message & let caller review or re-record it, or set options if applicable */ @@ -6705,7 +6753,7 @@ /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */ if (record_gain) ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0); - cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, silencethreshold, maxsilence, unlockdir); + cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, silencethreshold, maxsilence, NULL); if (record_gain) ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0); if (cmd == -1) { @@ -6822,6 +6870,129 @@ } +struct ast_vm_message *ast_vm_message_create(struct ast_vm_user *vmu, struct ast_channel *chan, const char *fmt) +{ + struct ast_vm_message *message; + + message = (struct ast_vm_message *) malloc(sizeof(struct ast_vm_message)); + + strncpy(message->context, vmu->context, sizeof(message->context)); + strncpy(message->ext, vmu->ext, sizeof(message->ext)); + strncpy(message->fmt, fmt, sizeof(message->fmt)); + message->chan = chan; + + /* It's easier just to try to make it than to check for its existence */ + if (!create_dirpath(message->dir, sizeof(message->dir), vmu->context, vmu->ext, "INBOX")) { + free(message); + return NULL; + } + + if (vm_lock_path(message->dir)) { + ast_log(LOG_WARNING, "Can't lock directory %s\n", message->dir); + free(message); + return NULL; + } + + message->msgnum = 0; + do { + make_file(message->fn, sizeof(message->fn), message->dir, message->msgnum); + if (!storage->message_exists(message->dir,message->msgnum,message->fn,chan->language)) + break; + message->msgnum++; + } while (message->msgnum < vmu->maxmsg); + if (message->msgnum >= vmu->maxmsg) { + ast_log(LOG_WARNING, "Mailbox full writing message to %s\n", message->dir); + ast_unlock_path(message->dir); + free(message); + return NULL; + } + + make_file(message->fn, sizeof(message->fn), message->dir, message->msgnum); + message->user = vmu; + message->flags = (VM_MSG_FILE_RETRIEVED | VM_MSG_IS_NEW); + message->duration = 0; /* HACK */ + + return message; +} + +/* FIXME deadlock when copying a message to the same directory as the source */ +struct ast_vm_message *ast_vm_message_create_copy(struct ast_vm_message *src, struct ast_vm_user *recip, struct ast_channel *chan, char *fmt) +{ + struct ast_vm_message *dest; + + ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", src->ext, src->context, recip->ext, recip->context); + + dest = ast_vm_message_create(recip, chan, fmt); + if (dest) { + storage->copy_file(src->dir, src->msgnum, dest->dir, dest->msgnum, recip->ext, recip->context, src->fn, dest->fn); + dest->duration = src->duration; + } else + ast_log(LOG_ERROR, "Can't copy message from %s@%s to %s@%s\n", src->ext, src->context, recip->ext, recip->context); + + return dest; +} + +void ast_vm_message_release(struct ast_vm_message *message) +{ + if (message->flags & VM_MSG_IS_NEW) { + /* FIXME eliminating ast_fileexists test... do we need it? */ + storage->store_file(message->dir, message->ext, message->context, message->msgnum); + notify_new_message(message->chan, message->user, message->msgnum, message->duration, message->fmt, message->chan->cid.cid_num, message->chan->cid.cid_name); + } + if (message->flags & VM_MSG_FILE_RETRIEVED) + storage->dispose_file(message->dir, message->msgnum); + ast_unlock_path(message->dir); + free(message); +} + +void ast_vm_message_delete_and_release(struct ast_vm_message *message) +{ + /* FIXME only works for newly-created messages for now */ + if (message->flags & VM_MSG_IS_NEW) + storage->delete_file(message->dir,message->msgnum,message->fn); + ast_unlock_path(message->dir); + free(message); +} + +/* FIXME format handling could be better */ +int ast_vm_message_get_data(struct ast_vm_message *message, char *filename, int filenamelen, char *fmt) +{ + if (!(message->flags & VM_MSG_FILE_RETRIEVED)) { + storage->retrieve_file(message->dir, message->msgnum); + message->flags |= VM_MSG_FILE_RETRIEVED; + } + if (fmt == NULL) + fmt = message->fmt; + if (fmt == "") { + strncpy(filename, message->fn, filenamelen); + return 0; + } else { + char one_fmt[80]; + char *p, *q; + strncpy(one_fmt, fmt, sizeof(one_fmt)); + q = strsep(&p, "|"); + snprintf(filename, filenamelen, "%s.%s", message->fn, q); + return 0; + } +} + +void ast_vm_message_printf_metadata(struct ast_vm_message *message, const char *fmt, ...) +{ + va_list vars; + FILE *txt; + char txtfile[256]; + + snprintf(txtfile, sizeof(txtfile), "%s.txt", message->fn); + txt = fopen(txtfile, "w+"); + if (txt) { + va_start(vars, fmt); + vfprintf(txt, fmt, vars); + va_end(vars); + fclose(txt); + } else + ast_log(LOG_WARNING, "Error opening text file %s for output\n", txtfile); +} + int usecount(void) { int res;