diff options
author | rizzo <rizzo@f38db490-d61c-443f-a65b-d21fe96a405b> | 2006-04-04 12:59:25 +0000 |
---|---|---|
committer | rizzo <rizzo@f38db490-d61c-443f-a65b-d21fe96a405b> | 2006-04-04 12:59:25 +0000 |
commit | 217ea2f1ea680d457c19aac1b563077f1f9ae67c (patch) | |
tree | c2ca953ad9722e235aa0205ca61e6b6a59c5e9e5 /file.c | |
parent | 577bc5cf68911578b9a529d3919a923f5a5b3436 (diff) |
Largely simplify format handlers (for file copy etc.)
collecting common functions in a single place and removing
them from the individual handlers.
The full description is on mantis,
http://bugs.digium.com/view.php?id=6375
and only the ogg_vorbis handler needs to be converted to
the new structure.
As a result of this change, format_au.c and format_pcm_alaw.c
should go away (in a separate commit) as their functionality
(trivial) has been merged in another file.
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@17243 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'file.c')
-rw-r--r-- | file.c | 1136 |
1 files changed, 501 insertions, 635 deletions
@@ -51,100 +51,65 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/app.h" #include "asterisk/pbx.h" #include "asterisk/linkedlists.h" +#include "asterisk/module.h" /* ast_update_use_count() */ -struct ast_format { - /*! Name of format */ - char name[80]; - /*! Extensions (separated by | if more than one) - this format can read. First is assumed for writing (e.g. .mp3) */ - char exts[80]; - /*! Format of frames it uses/provides (one only) */ - int format; - /*! Open an input stream, and start playback */ - struct ast_filestream * (*open)(FILE * f); - /*! Open an output stream, of a given file descriptor and comment it appropriately if applicable */ - struct ast_filestream * (*rewrite)(FILE *f, const char *comment); - /*! Write a frame to a channel */ - int (*write)(struct ast_filestream *, struct ast_frame *); - /*! seek num samples into file, whence(think normal seek) */ - int (*seek)(struct ast_filestream *, off_t offset, int whence); - /*! trunc file to current position */ - int (*trunc)(struct ast_filestream *fs); - /*! tell current position */ - off_t (*tell)(struct ast_filestream *fs); - /*! Read the next frame from the filestream (if available) and report when to get next one - (in samples) */ - struct ast_frame * (*read)(struct ast_filestream *, int *whennext); - /*! Close file, and destroy filestream structure */ - void (*close)(struct ast_filestream *); - /*! Retrieve file comment */ - char * (*getcomment)(struct ast_filestream *); - /*! Link */ - AST_LIST_ENTRY(ast_format) list; -}; - -struct ast_filestream { - /*! Everybody reserves a block of AST_RESERVED_POINTERS pointers for us */ - struct ast_format *fmt; - int flags; - mode_t mode; - char *filename; - char *realfilename; - /*! Video file stream */ - struct ast_filestream *vfs; - /*! Transparently translate from another format -- just once */ - struct ast_trans_pvt *trans; - struct ast_tranlator_pvt *tr; - int lastwriteformat; - int lasttimeout; - struct ast_channel *owner; -}; +/* + * The following variable controls the layout of localized sound files. + * If 0, use the historical layout with prefix just before the filename + * (i.e. digits/en/1.gsm , digits/it/1.gsm or default to digits/1.gsm), + * if 1 put the prefix at the beginning of the filename + * (i.e. en/digits/1.gsm, it/digits/1.gsm or default to digits/1.gsm). + * The latter permits a language to be entirely in one directory. + */ +int ast_language_is_prefix; static AST_LIST_HEAD_STATIC(formats, ast_format); -int ast_format_register(const char *name, const char *exts, int format, - struct ast_filestream * (*open)(FILE *f), - struct ast_filestream * (*rewrite)(FILE *f, const char *comment), - int (*write)(struct ast_filestream *, struct ast_frame *), - int (*seek)(struct ast_filestream *, off_t sample_offset, int whence), - int (*trunc)(struct ast_filestream *), - off_t (*tell)(struct ast_filestream *), - struct ast_frame * (*read)(struct ast_filestream *, int *whennext), - void (*close)(struct ast_filestream *), - char * (*getcomment)(struct ast_filestream *)) +int ast_format_register(const struct ast_format *f) { struct ast_format *tmp; + + if (f->lockp == NULL) { + ast_log(LOG_WARNING, "Missing lock pointer, you need to supply one\n"); + return -1; + } if (AST_LIST_LOCK(&formats)) { ast_log(LOG_WARNING, "Unable to lock format list\n"); return -1; } AST_LIST_TRAVERSE(&formats, tmp, list) { - if (!strcasecmp(name, tmp->name)) { + if (!strcasecmp(f->name, tmp->name)) { AST_LIST_UNLOCK(&formats); - ast_log(LOG_WARNING, "Tried to register '%s' format, already registered\n", name); + ast_log(LOG_WARNING, "Tried to register '%s' format, already registered\n", f->name); return -1; } - } - if (!(tmp = ast_malloc(sizeof(*tmp)))) { + } + tmp = ast_calloc(1, sizeof(struct ast_format)); + if (!tmp) { AST_LIST_UNLOCK(&formats); return -1; } - ast_copy_string(tmp->name, name, sizeof(tmp->name)); - ast_copy_string(tmp->exts, exts, sizeof(tmp->exts)); - tmp->open = open; - tmp->rewrite = rewrite; - tmp->read = read; - tmp->write = write; - tmp->seek = seek; - tmp->trunc = trunc; - tmp->tell = tell; - tmp->close = close; - tmp->format = format; - tmp->getcomment = getcomment; + *tmp = *f; + if (tmp->buf_size) { + /* + * Align buf_size properly, rounding up to the machine-specific + * alignment for pointers. + */ + struct _test_align { void *a, *b; } p; + int align = (char *)&p.b - (char *)&p.a; + tmp->buf_size = ((f->buf_size + align - 1)/align)*align; + } + + memset(&tmp->list, 0, sizeof(tmp->list)); + if (tmp->lockp->usecnt < 0) { + ast_mutex_init(&tmp->lockp->lock); + tmp->lockp->usecnt = 0; + } + AST_LIST_INSERT_HEAD(&formats, tmp, list); AST_LIST_UNLOCK(&formats); if (option_verbose > 1) - ast_verbose( VERBOSE_PREFIX_2 "Registered file format %s, extension(s) %s\n", name, exts); + ast_verbose( VERBOSE_PREFIX_2 "Registered file format %s, extension(s) %s\n", f->name, f->exts); return 0; } @@ -169,7 +134,7 @@ int ast_format_unregister(const char *name) if (tmp) { if (option_verbose > 1) - ast_verbose( VERBOSE_PREFIX_2 "Unregistered format %s\n", name); + ast_verbose( VERBOSE_PREFIX_2 "Unregistered format %s\n", name); } else ast_log(LOG_WARNING, "Tried to unregister format %s, already unregistered\n", name); @@ -189,9 +154,8 @@ int ast_stopstream(struct ast_channel *tmp) int ast_writestream(struct ast_filestream *fs, struct ast_frame *f) { - struct ast_frame *trf; int res = -1; - int alt=0; + int alt = 0; if (f->frametype == AST_FRAME_VIDEO) { if (fs->fmt->format < AST_FORMAT_MAX_AUDIO) { /* This is the audio portion. Call the video one... */ @@ -202,7 +166,7 @@ int ast_writestream(struct ast_filestream *fs, struct ast_frame *f) } if (fs->vfs) return ast_writestream(fs->vfs, f); - /* Ignore */ + /* else ignore */ return 0; } else { /* Might / might not have mark set */ @@ -216,23 +180,23 @@ int ast_writestream(struct ast_filestream *fs, struct ast_frame *f) res = fs->fmt->write(fs, f); if (res < 0) ast_log(LOG_WARNING, "Natural write failed\n"); - if (res > 0) + else if (res > 0) ast_log(LOG_WARNING, "Huh??\n"); - return res; } else { /* XXX If they try to send us a type of frame that isn't the normal frame, and isn't the one we've setup a translator for, we do the "wrong thing" XXX */ - if (fs->trans && (f->subclass != fs->lastwriteformat)) { + if (fs->trans && f->subclass != fs->lastwriteformat) { ast_translator_free_path(fs->trans); fs->trans = NULL; } if (!fs->trans) fs->trans = ast_translator_build_path(fs->fmt->format, f->subclass); if (!fs->trans) - ast_log(LOG_WARNING, "Unable to translate to format %s, source format %s\n", fs->fmt->name, ast_getformatname(f->subclass)); + ast_log(LOG_WARNING, "Unable to translate to format %s, source format %s\n", + fs->fmt->name, ast_getformatname(f->subclass)); else { + struct ast_frame *trf; fs->lastwriteformat = f->subclass; - res = 0; /* Get the translated frame but don't consume the original in case they're using it on another stream */ trf = ast_translate(fs->trans, f, 0); if (trf) { @@ -242,17 +206,14 @@ int ast_writestream(struct ast_filestream *fs, struct ast_frame *f) } else res = 0; } - return res; } + return res; } static int copy(const char *infile, const char *outfile) { - int ifd; - int ofd; - int res; - int len; - char buf[4096]; + int ifd, ofd, len; + char buf[4096]; /* XXX make it lerger. */ if ((ifd = open(infile, O_RDONLY)) < 0) { ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile); @@ -263,187 +224,312 @@ static int copy(const char *infile, const char *outfile) close(ifd); return -1; } - do { - len = read(ifd, buf, sizeof(buf)); + while ( (len = read(ifd, buf, sizeof(buf)) ) ) { + int res; if (len < 0) { ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno)); - close(ifd); - close(ofd); - unlink(outfile); + break; } - if (len) { - res = write(ofd, buf, len); - if (res != len) { - ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno)); - close(ifd); - close(ofd); - unlink(outfile); - } + /* XXX handle partial writes */ + res = write(ofd, buf, len); + if (res != len) { + ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno)); + len = -1; /* error marker */ + break; } - } while(len); + } close(ifd); close(ofd); - return 0; + if (len < 0) { + unlink(outfile); + return -1; /* error */ + } + return 0; /* success */ } +/*! + * \brief construct a filename. Absolute pathnames are preserved, + * relative names are prefixed by the sounds/ directory. + * The wav49 suffix is replaced by 'WAV'. + * Returns a malloc'ed string to be freed by the caller. + */ static char *build_filename(const char *filename, const char *ext) { - char *fn, type[16]; - int fnsize = 0; + char *fn = NULL; - if (!strcmp(ext, "wav49")) { - ast_copy_string(type, "WAV", sizeof(type)); - } else { - ast_copy_string(type, ext, sizeof(type)); - } - - if (filename[0] == '/') { - fnsize = strlen(filename) + strlen(type) + 2; - if ((fn = ast_malloc(fnsize))) - snprintf(fn, fnsize, "%s.%s", filename, type); - } else { - char tmp[AST_CONFIG_MAX_PATH] = ""; - - snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_VAR_DIR, "sounds"); - fnsize = strlen(tmp) + strlen(filename) + strlen(type) + 3; - if ((fn = ast_malloc(fnsize))) - snprintf(fn, fnsize, "%s/%s.%s", tmp, filename, type); - } + if (!strcmp(ext, "wav49")) + ext = "WAV"; + if (filename[0] == '/') + asprintf(&fn, "%s.%s", filename, ext); + else + asprintf(&fn, "%s/sounds/%s.%s", + ast_config_AST_VAR_DIR, filename, ext); return fn; } +/* compare type against the list 'exts' */ +/* XXX need a better algorithm */ static int exts_compare(const char *exts, const char *type) { - char *stringp = NULL, *ext; char tmp[256]; + char *stringp = tmp, *ext; ast_copy_string(tmp, exts, sizeof(tmp)); - stringp = tmp; while ((ext = strsep(&stringp, "|"))) { - if (!strcmp(ext, type)) { + if (!strcmp(ext, type)) return 1; - } } return 0; } +static struct ast_filestream *get_filestream(struct ast_format *fmt, FILE *bfile) +{ + struct ast_filestream *s; + + int l = sizeof(*s) + fmt->buf_size + fmt->desc_size; /* total allocation size */ + if ( (s = ast_calloc(1, l)) == NULL) + return NULL; + s->fmt = fmt; + s->f = bfile; + + if (fmt->desc_size) + s->private = ((char *)(s+1)) + fmt->buf_size; + if (fmt->buf_size) + s->buf = (char *)(s+1); + s->fr.src = fmt->name; + return s; +} + +/* + * Default implementations of open and rewrite. + * Only use them if you don't have expensive stuff to do. + */ +enum wrap_fn { WRAP_OPEN, WRAP_REWRITE }; + +static int fn_wrapper(struct ast_filestream *s, const char *comment, enum wrap_fn mode) +{ + struct ast_format *f = s->fmt; + int ret = -1; + + if (mode == WRAP_OPEN && f->open && f->open(s)) + ast_log(LOG_WARNING, "Unable to open format %s\n", f->name); + else if (mode == WRAP_REWRITE && f->rewrite && f->rewrite(s, comment)) + ast_log(LOG_WARNING, "Unable to rewrite format %s\n", f->name); + else { + /* preliminary checks succeed. update usecount */ + if (ast_mutex_lock(&f->lockp->lock)) { + ast_log(LOG_WARNING, "Unable to lock format %s\n", f->name); + return -1; + } + f->lockp->usecnt++; + ast_mutex_unlock(&f->lockp->lock); + ret = 0; + ast_update_use_count(); + } + return ret; +} + +static int rewrite_wrapper(struct ast_filestream *s, const char *comment) +{ + return fn_wrapper(s, comment, WRAP_REWRITE); +} + +static int open_wrapper(struct ast_filestream *s) +{ + return fn_wrapper(s, NULL, WRAP_OPEN); +} + enum file_action { - ACTION_EXISTS = 1, - ACTION_DELETE, - ACTION_RENAME, + ACTION_EXISTS = 1, /* return matching format if file exists, 0 otherwise */ + ACTION_DELETE, /* delete file, return 0 on success, -1 on error */ + ACTION_RENAME, /* rename file. return 0 on success, -1 on error */ ACTION_OPEN, - ACTION_COPY + ACTION_COPY /* copy file. return 0 on success, -1 on error */ }; -static int ast_filehelper(const char *filename, const char *filename2, const char *fmt, const enum file_action action) +/*! + * \brief perform various actions on a file. Second argument + * arg2 depends on the command: + * unused for EXISTS and DELETE + * destination file name (const char *) for COPY and RENAME + * struct ast_channel * for OPEN + */ +static int ast_filehelper(const char *filename, const void *arg2, const char *fmt, const enum file_action action) { - struct stat st; struct ast_format *f; - struct ast_filestream *s; - int res=0, ret = 0; - char *ext=NULL, *exts, *fn, *nfn; - FILE *bfile; - struct ast_channel *chan = (struct ast_channel *)filename2; - - /* Start with negative response */ - if (action == ACTION_EXISTS) - res = 0; - else - res = -1; - if (action == ACTION_OPEN) - ret = -1; - /* Check for a specific format */ + char *ext = NULL, *fn = NULL; + int res = (action == ACTION_EXISTS) ? 0 : -1; + if (AST_LIST_LOCK(&formats)) { ast_log(LOG_WARNING, "Unable to lock format list\n"); return res; } + /* Check for a specific format */ AST_LIST_TRAVERSE(&formats, f, list) { - if (!fmt || exts_compare(f->exts, fmt)) { - char *stringp=NULL; - exts = ast_strdupa(f->exts); - /* Try each kind of extension */ - stringp=exts; - ext = strsep(&stringp, "|"); - do { - fn = build_filename(filename, ext); - if (fn) { - res = stat(fn, &st); - if (!res) { - switch(action) { - case ACTION_EXISTS: - ret |= f->format; - break; - case ACTION_DELETE: - res = unlink(fn); - if (res) - ast_log(LOG_WARNING, "unlink(%s) failed: %s\n", fn, strerror(errno)); - break; - case ACTION_RENAME: - nfn = build_filename(filename2, ext); - if (nfn) { - res = rename(fn, nfn); - if (res) - ast_log(LOG_WARNING, "rename(%s,%s) failed: %s\n", fn, nfn, strerror(errno)); - free(nfn); - } - break; - case ACTION_COPY: - nfn = build_filename(filename2, ext); - if (nfn) { - res = copy(fn, nfn); - if (res) - ast_log(LOG_WARNING, "copy(%s,%s) failed: %s\n", fn, nfn, strerror(errno)); - free(nfn); - } - break; - case ACTION_OPEN: - if ((ret < 0) && ((chan->writeformat & f->format) || - ((f->format >= AST_FORMAT_MAX_AUDIO) && fmt))) { - bfile = fopen(fn, "r"); - if (bfile) { - ret = 1; - s = f->open(bfile); - if (s) { - s->lasttimeout = -1; - s->fmt = f; - s->trans = NULL; - s->filename = NULL; - if (s->fmt->format < AST_FORMAT_MAX_AUDIO) - chan->stream = s; - else - chan->vstream = s; - } else { - fclose(bfile); - ast_log(LOG_WARNING, "Unable to open file on %s\n", fn); - ret = -1; - } - } else{ - ast_log(LOG_WARNING, "Couldn't open file %s\n", fn); - ret = -1; - } - } - break; - default: - ast_log(LOG_WARNING, "Unknown helper %d\n", action); - } - /* Conveniently this logic is the same for all */ - if (res) - break; - } + char *stringp; + + if (fmt && !exts_compare(f->exts, fmt)) + continue; + + /* Look for a file matching the supported extensions. + * The file must exist, and for OPEN, must match + * one of the formats supported by the channel. + */ + stringp = ast_strdupa(f->exts); + while ( (ext = strsep(&stringp, "|")) ) { + struct stat st; + fn = build_filename(filename, ext); + if (fn == NULL) + continue; + + if ( stat(fn, &st) ) { /* file not existent */ + free(fn); + continue; + } + /* for 'OPEN' we need to be sure that the format matches + * what the channel can process + */ + if (action == ACTION_OPEN) { + struct ast_channel *chan = (struct ast_channel *)arg2; + FILE *bfile; + struct ast_filestream *s; + + if ( !(chan->writeformat & f->format) && + !(f->format >= AST_FORMAT_MAX_AUDIO && fmt)) { free(fn); + continue; /* not a supported format */ + } + if ( (bfile = fopen(fn, "r")) == NULL) { + free(fn); + continue; /* cannot open file */ + } + s = get_filestream(f, bfile); + if (!s) { + fclose(bfile); + free(fn); /* cannot allocate descriptor */ + continue; } - ext = strsep(&stringp, "|"); - } while(ext); - + if (open_wrapper(s)) { + fclose(bfile); + free(fn); + free(s); + continue; /* cannot run open on file */ + } + /* ok this is good for OPEN */ + res = 1; /* found */ + s->lasttimeout = -1; + s->fmt = f; + s->trans = NULL; + s->filename = NULL; + if (s->fmt->format < AST_FORMAT_MAX_AUDIO) + chan->stream = s; + else + chan->vstream = s; + } + break; /* found the file */ } + if (ext) + break; + } + if (ext) { /* break out on a valid 'ext', so fn is also valid */ + char *nfn; + + switch (action) { + case ACTION_EXISTS: /* return the matching format */ + res |= f->format; + break; + + case ACTION_DELETE: + if ( (res = unlink(fn)) ) + ast_log(LOG_WARNING, "unlink(%s) failed: %s\n", fn, strerror(errno)); + break; + case ACTION_RENAME: + case ACTION_COPY: + nfn = build_filename((const char *)arg2, ext); + if (!nfn) + ast_log(LOG_WARNING, "Out of memory\n"); + else { + res = action == ACTION_COPY ? copy(fn, nfn) : rename(fn, nfn); + if (res) + ast_log(LOG_WARNING, "%s(%s,%s) failed: %s\n", + action == ACTION_COPY ? "copy" : "rename", + fn, nfn, strerror(errno)); + free(nfn); + } + break; + case ACTION_OPEN: /* all done already! */ + break; + default: + ast_log(LOG_WARNING, "Unknown helper %d\n", action); + } + free(fn); } AST_LIST_UNLOCK(&formats); - if ((action == ACTION_EXISTS) || (action == ACTION_OPEN)) - res = ret ? ret : -1; return res; } + +/*! + * \brief helper routine to locate a file with a given format + * and language preference. + * Try preflang, preflang with stripped '_' suffix, or NULL. + * In the standard asterisk, language goes just before the last component. + * In an alternative configuration, the language should be a prefix + * to the actual filename. + * + * The last parameter(s) point to a buffer of sufficient size, + * which on success is filled with the matching filename. + */ +static int fileexists_core(const char *filename, const char *fmt, const char *preflang, + char *buf, int buflen) +{ + int res = -1; + int langlen; /* length of language string */ + const char *c = strrchr(filename, '/'); + int offset = c ? c - filename + 1 : 0; /* points right after the last '/' */ + + if (preflang == NULL) + preflang = ""; + langlen = strlen(preflang); + + if (buflen < langlen + strlen(filename) + 2) { + ast_log(LOG_WARNING, "buffer too small\n"); + buf[0] = '\0'; /* set to empty */ + buf = alloca(langlen + strlen(filename) + 2); /* room for everything */ + } + if (buf == NULL) + return 0; + buf[0] = '\0'; + for (;;) { + if (ast_language_is_prefix) { /* new layout */ + if (langlen) { + strcpy(buf, preflang); + buf[langlen] = '/'; + strcpy(buf + langlen + 1, filename); + } else + strcpy(buf, filename); /* first copy the full string */ + } else { /* old layout */ + strcpy(buf, filename); /* first copy the full string */ + if (langlen) { + /* insert the language and suffix if needed */ + strcpy(buf + offset, preflang); + sprintf(buf + offset + langlen, "/%s", filename + offset); + } + } + res = ast_filehelper(buf, NULL, fmt, ACTION_EXISTS); + if (res > 0) /* found format */ + break; + if (langlen == 0) /* no more formats */ + break; + if (preflang[langlen] == '_') /* we are on the local suffix */ + langlen = 0; /* try again with no language */ + else + langlen = (c = strchr(preflang, '_')) ? c - preflang : 0; + } + return res; +} + struct ast_filestream *ast_openstream(struct ast_channel *chan, const char *filename, const char *preflang) { return ast_openstream_full(chan, filename, preflang, 0); @@ -451,23 +537,13 @@ struct ast_filestream *ast_openstream(struct ast_channel *chan, const char *file struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char *filename, const char *preflang, int asis) { - /* This is a fairly complex routine. Essentially we should do - the following: - - 1) Find which file handlers produce our type of format. - 2) Look for a filename which it can handle. - 3) If we find one, then great. - 4) If not, see what files are there - 5) See what we can actually support - 6) Choose the one with the least costly translator path and - set it up. - - */ - int fmts = -1; - char filename2[256]=""; - char filename3[256]; - char *endpart; - int res; + /* + * Use fileexists_core() to find a file in a compatible + * language and format, set up a suitable translator, + * and open the stream. + */ + int fmts, res, buflen; + char *buf; if (!asis) { /* do this first, otherwise we detect the wrong writeformat */ @@ -475,25 +551,15 @@ struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char if (chan->generator) ast_deactivate_generator(chan); } - if (!ast_strlen_zero(preflang)) { - ast_copy_string(filename3, filename, sizeof(filename3)); - endpart = strrchr(filename3, '/'); - if (endpart) { - *endpart = '\0'; - endpart++; - snprintf(filename2, sizeof(filename2), "%s/%s/%s", filename3, preflang, endpart); - } else - snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename); - fmts = ast_fileexists(filename2, NULL, NULL); - if (fmts > 0) - fmts &= AST_FORMAT_AUDIO_MASK; - } - if (fmts < 1) { - ast_copy_string(filename2, filename, sizeof(filename2)); - fmts = ast_fileexists(filename2, NULL, NULL); - if (fmts > 0) - fmts &= AST_FORMAT_AUDIO_MASK; - } + if (preflang == NULL) + preflang = ""; + buflen = strlen(preflang) + strlen(filename) + 2; + buf = alloca(buflen); + if (buf == NULL) + return NULL; + fmts = fileexists_core(filename, NULL, preflang, buf, buflen); + if (fmts > 0) + fmts &= AST_FORMAT_AUDIO_MASK; if (fmts < 1) { ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename); return NULL; @@ -501,8 +567,7 @@ struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char chan->oldwriteformat = chan->writeformat; /* Set the channel to a format we can work with */ res = ast_set_write_format(chan, fmts); - - res = ast_filehelper(filename2, (char *)chan, NULL, ACTION_OPEN); + res = ast_filehelper(buf, chan, NULL, ACTION_OPEN); if (res >= 0) return chan->stream; return NULL; @@ -510,45 +575,30 @@ struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *filename, const char *preflang) { - /* This is a fairly complex routine. Essentially we should do - the following: - - 1) Find which file handlers produce our type of format. - 2) Look for a filename which it can handle. - 3) If we find one, then great. - 4) If not, see what files are there - 5) See what we can actually support - 6) Choose the one with the least costly translator path and - set it up. - - */ - int fd = -1; - int fmts = -1; + /* As above, but for video. But here we don't have translators + * so we must enforce a format. + */ unsigned int format; - char filename2[256]; - char lang2[MAX_LANGUAGE]; - const char *fmt; + char *buf; + int buflen; + + if (preflang == NULL) + preflang = ""; + buflen = strlen(preflang) + strlen(filename) + 2; + buf = alloca(buflen); + if (buf == NULL) + return NULL; + for (format = AST_FORMAT_MAX_AUDIO << 1; format <= AST_FORMAT_MAX_VIDEO; format = format << 1) { + int fd; + const char *fmt; + if (!(chan->nativeformats & format)) continue; fmt = ast_getformatname(format); - if (!ast_strlen_zero(preflang)) { - snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename); - fmts = ast_fileexists(filename2, fmt, NULL); - if (fmts < 1) { - ast_copy_string(lang2, preflang, sizeof(lang2)); - snprintf(filename2, sizeof(filename2), "%s/%s", lang2, filename); - fmts = ast_fileexists(filename2, fmt, NULL); - } - } - if (fmts < 1) { - ast_copy_string(filename2, filename, sizeof(filename2)); - fmts = ast_fileexists(filename2, fmt, NULL); - } - if (fmts < 1) { + if ( fileexists_core(filename, fmt, preflang, buf, buflen) < 1) /* no valid format */ continue; - } - fd = ast_filehelper(filename2, (char *)chan, fmt, ACTION_OPEN); + fd = ast_filehelper(buf, chan, fmt, ACTION_OPEN); if (fd >= 0) return chan->vstream; ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename); @@ -568,22 +618,13 @@ struct ast_frame *ast_readframe(struct ast_filestream *s) static int ast_readaudio_callback(void *data) { struct ast_filestream *s = data; - struct ast_frame *fr; int whennext = 0; while(!whennext) { - fr = s->fmt->read(s, &whennext); - if (fr) { - if (ast_write(s->owner, fr)) { + struct ast_frame *fr = s->fmt->read(s, &whennext); + if (!fr /* stream complete */ || ast_write(s->owner, fr) /* error writing */) { + if (fr) ast_log(LOG_WARNING, "Failed to write frame\n"); - s->owner->streamid = -1; -#ifdef ZAPTEL_OPTIMIZATIONS - ast_settimeout(s->owner, 0, NULL, NULL); -#endif - return 0; - } - } else { - /* Stream has finished */ s->owner->streamid = -1; #ifdef ZAPTEL_OPTIMIZATIONS ast_settimeout(s->owner, 0, NULL, NULL); @@ -607,19 +648,13 @@ static int ast_readaudio_callback(void *data) static int ast_readvideo_callback(void *data) { struct ast_filestream *s = data; - struct ast_frame *fr; int whennext = 0; - while(!whennext) { - fr = s->fmt->read(s, &whennext); - if (fr) { - if (ast_write(s->owner, fr)) { + while (!whennext) { + struct ast_frame *fr = s->fmt->read(s, &whennext); + if (!fr || ast_write(s->owner, fr)) { /* no stream or error, as above */ + if (fr) ast_log(LOG_WARNING, "Failed to write frame\n"); - s->owner->vstreamid = -1; - return 0; - } - } else { - /* Stream has finished */ s->owner->vstreamid = -1; return 0; } @@ -664,17 +699,12 @@ off_t ast_tellstream(struct ast_filestream *fs) int ast_stream_fastforward(struct ast_filestream *fs, off_t ms) { - /* I think this is right, 8000 samples per second, 1000 ms a second so 8 - * samples per ms */ - off_t samples = ms * 8; - return ast_seekstream(fs, samples, SEEK_CUR); + return ast_seekstream(fs, ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR); } int ast_stream_rewind(struct ast_filestream *fs, off_t ms) { - off_t samples = ms * 8; - samples = samples * -1; - return ast_seekstream(fs, samples, SEEK_CUR); + return ast_seekstream(fs, -ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR); } int ast_closestream(struct ast_filestream *f) @@ -699,10 +729,8 @@ int ast_closestream(struct ast_filestream *f) } } /* destroy the translator on exit */ - if (f->trans) { + if (f->trans) ast_translator_free_path(f->trans); - f->trans = NULL; - } if (f->realfilename && f->filename) { size = strlen(f->filename) + strlen(f->realfilename) + 15; @@ -712,69 +740,42 @@ int ast_closestream(struct ast_filestream *f) ast_safe_system(cmd); } - if (f->filename) { + if (f->filename) free(f->filename); - f->filename = NULL; - } - if (f->realfilename) { + if (f->realfilename) free(f->realfilename); - f->realfilename = NULL; - } - if (f->vfs) { + if (f->fmt->close) + f->fmt->close(f); + fclose(f->f); + if (f->vfs) ast_closestream(f->vfs); - f->vfs = NULL; + if (ast_mutex_lock(&f->fmt->lockp->lock)) { + ast_log(LOG_WARNING, "Unable to lock format %s\n", f->fmt->name); + } else { + f->fmt->lockp->usecnt--; + ast_mutex_unlock(&f->fmt->lockp->lock); + ast_update_use_count(); } - f->fmt->close(f); + free(f); return 0; } +/* + * Look the various language-specific places where a file could exist. + */ int ast_fileexists(const char *filename, const char *fmt, const char *preflang) { - char filename2[256]; - char tmp[256]; - char *postfix; - char *prefix; - char *c; - char lang2[MAX_LANGUAGE]; - int res = -1; - if (!ast_strlen_zero(preflang)) { - /* Insert the language between the last two parts of the path */ - ast_copy_string(tmp, filename, sizeof(tmp)); - c = strrchr(tmp, '/'); - if (c) { - *c = '\0'; - postfix = c+1; - prefix = tmp; - snprintf(filename2, sizeof(filename2), "%s/%s/%s", prefix, preflang, postfix); - } else { - postfix = tmp; - prefix=""; - snprintf(filename2, sizeof(filename2), "%s/%s", preflang, postfix); - } - res = ast_filehelper(filename2, NULL, fmt, ACTION_EXISTS); - if (res < 1) { - char *stringp=NULL; - ast_copy_string(lang2, preflang, sizeof(lang2)); - stringp=lang2; - strsep(&stringp, "_"); - /* If language is a specific locality of a language (like es_MX), strip the locality and try again */ - if (strcmp(lang2, preflang)) { - if (ast_strlen_zero(prefix)) { - snprintf(filename2, sizeof(filename2), "%s/%s", lang2, postfix); - } else { - snprintf(filename2, sizeof(filename2), "%s/%s/%s", prefix, lang2, postfix); - } - res = ast_filehelper(filename2, NULL, fmt, ACTION_EXISTS); - } - } - } - - /* Fallback to no language (usually winds up being American English) */ - if (res < 1) { - res = ast_filehelper(filename, NULL, fmt, ACTION_EXISTS); - } - return res; + char *buf; + int buflen; + + if (preflang == NULL) + preflang = ""; + buflen = strlen(preflang) + strlen(filename) + 2; /* room for everything */ + buf = alloca(buflen); + if (buf == NULL) + return 0; + return fileexists_core(filename, fmt, preflang, buf, buflen); } int ast_filedelete(const char *filename, const char *fmt) @@ -835,33 +836,30 @@ struct ast_filestream *ast_readfile(const char *filename, const char *type, cons } AST_LIST_TRAVERSE(&formats, f, list) { - if (fs) - break; - + fs = NULL; if (!exts_compare(f->exts, type)) continue; fn = build_filename(filename, type); + errno = 0; bfile = fopen(fn, "r"); - if (bfile) { - errno = 0; - - if (!(fs = f->open(bfile))) { - ast_log(LOG_WARNING, "Unable to open %s\n", fn); - fclose(bfile); - free(fn); - continue; - } - - fs->trans = NULL; - fs->fmt = f; - fs->flags = flags; - fs->mode = mode; - fs->filename = strdup(filename); - fs->vfs = NULL; - } else if (errno != EEXIST) - ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno)); - free(fn); + if (!bfile || (fs = get_filestream(f, bfile)) == NULL || + open_wrapper(fs) ) { + ast_log(LOG_WARNING, "Unable to open %s\n", fn); + fclose(bfile); + free(fn); + if (fs) + free(fs); + continue; + } + /* found it */ + fs->trans = NULL; + fs->fmt = f; + fs->flags = flags; + fs->mode = mode; + fs->filename = strdup(filename); + fs->vfs = NULL; + break; } AST_LIST_UNLOCK(&formats); @@ -878,7 +876,6 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con FILE *bfile = NULL; struct ast_format *f; struct ast_filestream *fs = NULL; - char *fn, *orig_fn = NULL; char *buf = NULL; size_t size = 0; @@ -888,8 +885,8 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con } /* set the O_TRUNC flag if and only if there is no O_APPEND specified */ + /* We really can't use O_APPEND as it will break WAV header updates */ if (flags & O_APPEND) { - /* We really can't use O_APPEND as it will break WAV header updates */ flags &= ~O_APPEND; } else { myflags = O_TRUNC; @@ -897,7 +894,11 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con myflags |= O_WRONLY | O_CREAT; + /* XXX need to fix this - we should just do the fopen, + * not open followed by fdopen() + */ AST_LIST_TRAVERSE(&formats, f, list) { + char *fn, *orig_fn = NULL; if (fs) break; @@ -919,7 +920,7 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con if (ast_opt_cache_record_files && (fd > -1)) { char *c; - fclose(bfile); + fclose(bfile); /* this also closes fd */ /* We touch orig_fn just as a place-holder so other things (like vmail) see the file is there. What we are really doing is writing to record_cache_dir until we are done then we will mv the file into place. @@ -949,29 +950,31 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con } if (fd > -1) { errno = 0; - if ((fs = f->rewrite(bfile, comment))) { - fs->trans = NULL; - fs->fmt = f; - fs->flags = flags; - fs->mode = mode; - if (orig_fn) { - fs->realfilename = strdup(orig_fn); - fs->filename = strdup(fn); - } else { - fs->realfilename = NULL; - fs->filename = strdup(filename); - } - fs->vfs = NULL; - /* If truncated, we'll be at the beginning; if not truncated, then append */ - f->seek(fs, 0, SEEK_END); - } else { + fs = get_filestream(f, bfile); + if (!fs || rewrite_wrapper(fs, comment)) { ast_log(LOG_WARNING, "Unable to rewrite %s\n", fn); close(fd); if (orig_fn) { unlink(fn); unlink(orig_fn); } + if (fs) + free(fs); + } + fs->trans = NULL; + fs->fmt = f; + fs->flags = flags; + fs->mode = mode; + if (orig_fn) { + fs->realfilename = strdup(orig_fn); + fs->filename = strdup(fn); + } else { + fs->realfilename = NULL; + fs->filename = strdup(filename); } + fs->vfs = NULL; + /* If truncated, we'll be at the beginning; if not truncated, then append */ + f->seek(fs, 0, SEEK_END); } else if (errno != EEXIST) { ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno)); if (orig_fn) @@ -989,176 +992,73 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con return fs; } -int ast_waitstream(struct ast_channel *c, const char *breakon) -{ - /* XXX Maybe I should just front-end ast_waitstream_full ? XXX */ - int res; - struct ast_frame *fr; - if (!breakon) breakon = ""; - while(c->stream) { - res = ast_sched_wait(c->sched); - if ((res < 0) && !c->timingfunc) { - ast_stopstream(c); - break; - } - if (res < 0) - res = 1000; - res = ast_waitfor(c, res); - if (res < 0) { - ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno)); - return res; - } else if (res > 0) { - fr = ast_read(c); - if (!fr) { -#if 0 - ast_log(LOG_DEBUG, "Got hung up\n"); -#endif - return -1; - } - - switch(fr->frametype) { - case AST_FRAME_DTMF: - res = fr->subclass; - if (strchr(breakon, res)) { - ast_frfree(fr); - return res; - } - break; - case AST_FRAME_CONTROL: - switch(fr->subclass) { - case AST_CONTROL_HANGUP: - ast_frfree(fr); - return -1; - case AST_CONTROL_RINGING: - case AST_CONTROL_ANSWER: - case AST_CONTROL_VIDUPDATE: - /* Unimportant */ - break; - default: - ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass); - } - } - /* Ignore */ - ast_frfree(fr); - } - ast_sched_runq(c->sched); - } - return (c->_softhangup ? -1 : 0); -} - -int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms) +/*! + * \brief the core of all waitstream() functions + */ +static int waitstream_core(struct ast_channel *c, const char *breakon, + const char *forward, const char *rewind, int skip_ms, + int audiofd, int cmdfd, const char *context) { - int res; - struct ast_frame *fr; - if (!breakon) - breakon = ""; + breakon = ""; if (!forward) - forward = ""; + forward = ""; if (!rewind) - rewind = ""; - - while(c->stream) { - res = ast_sched_wait(c->sched); - if ((res < 0) && !c->timingfunc) { - ast_stopstream(c); - break; - } - if (res < 0) - res = 1000; - res = ast_waitfor(c, res); - if (res < 0) { - ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno)); - return res; - } else - if (res > 0) { - fr = ast_read(c); - if (!fr) { -#if 0 - ast_log(LOG_DEBUG, "Got hung up\n"); -#endif - return -1; - } - - switch(fr->frametype) { - case AST_FRAME_DTMF: - res = fr->subclass; - if (strchr(forward,res)) { - ast_stream_fastforward(c->stream, ms); - } else if (strchr(rewind,res)) { - ast_stream_rewind(c->stream, ms); - } else if (strchr(breakon, res)) { - ast_frfree(fr); - return res; - } - break; - case AST_FRAME_CONTROL: - switch(fr->subclass) { - case AST_CONTROL_HANGUP: - ast_frfree(fr); - return -1; - case AST_CONTROL_RINGING: - case AST_CONTROL_ANSWER: - /* Unimportant */ - break; - default: - ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass); - } - } - /* Ignore */ - ast_frfree(fr); - } else - ast_sched_runq(c->sched); - - - } - return (c->_softhangup ? -1 : 0); -} - -int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd) -{ - int res; - int ms; - int outfd; - struct ast_frame *fr; - struct ast_channel *rchan; - - if (!breakon) - breakon = ""; + rewind = ""; - while(c->stream) { - ms = ast_sched_wait(c->sched); - if ((ms < 0) && !c->timingfunc) { + while (c->stream) { + int res; + int ms = ast_sched_wait(c->sched); + if (ms < 0 && !c->timingfunc) { ast_stopstream(c); break; } if (ms < 0) ms = 1000; - rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms); - if (!rchan && (outfd < 0) && (ms)) { - /* Continue */ - if (errno == EINTR) - continue; - ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno)); - return -1; - } else if (outfd > -1) { - /* The FD we were watching has something waiting */ - return 1; - } else if (rchan) { - fr = ast_read(c); - if (!fr) { -#if 0 - ast_log(LOG_DEBUG, "Got hung up\n"); -#endif + if (!cmdfd) { + res = ast_waitfor(c, ms); + if (res < 0) { + ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno)); + return res; + } + } else { + int outfd; + struct ast_channel *rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms); + if (!rchan && (outfd < 0) && (ms)) { + /* Continue */ + if (errno == EINTR) + continue; + ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno)); return -1; + } else if (outfd > -1) { /* this requires cmdfd set */ + /* The FD we were watching has something waiting */ + return 1; } - + /* if rchan is set, it is 'c' */ + res = rchan ? 1 : 0; /* map into 'res' values */ + } + if (res > 0) { + struct ast_frame *fr = ast_read(c); + if (!fr) + return -1; switch(fr->frametype) { case AST_FRAME_DTMF: - res = fr->subclass; - if (strchr(breakon, res)) { - ast_frfree(fr); - return res; + if (context) { + const char exten[2] = { fr->subclass, '\0' }; + if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) { + ast_frfree(fr); + return res; + } + } else { + res = fr->subclass; + if (strchr(forward,res)) { + ast_stream_fastforward(c->stream, skip_ms); + } else if (strchr(rewind,res)) { + ast_stream_rewind(c->stream, skip_ms); + } else if (strchr(breakon, res)) { + ast_frfree(fr); + return res; + } } break; case AST_FRAME_CONTROL: @@ -1178,7 +1078,7 @@ int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, if (audiofd > -1) write(audiofd, fr->data, fr->datalen); } - /* Ignore */ + /* Ignore all others */ ast_frfree(fr); } ast_sched_runq(c->sched); @@ -1186,65 +1086,32 @@ int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, return (c->_softhangup ? -1 : 0); } +int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms) +{ + return waitstream_core(c, breakon, forward, rewind, ms, + -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */); +} + +int ast_waitstream(struct ast_channel *c, const char *breakon) +{ + return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL); +} + +int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd) +{ + return waitstream_core(c, breakon, NULL, NULL, 0, + audiofd, cmdfd, NULL /* no context */); +} + int ast_waitstream_exten(struct ast_channel *c, const char *context) { /* Waitstream, with return in the case of a valid 1 digit extension */ /* in the current or specified context being pressed */ - /* XXX Maybe I should just front-end ast_waitstream_full ? XXX */ - int res; - struct ast_frame *fr; - char exten[AST_MAX_EXTENSION]; - - if (!context) context = c->context; - while(c->stream) { - res = ast_sched_wait(c->sched); - if ((res < 0) && !c->timingfunc) { - ast_stopstream(c); - break; - } - if (res < 0) - res = 1000; - res = ast_waitfor(c, res); - if (res < 0) { - ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno)); - return res; - } else if (res > 0) { - fr = ast_read(c); - if (!fr) { -#if 0 - ast_log(LOG_DEBUG, "Got hung up\n"); -#endif - return -1; - } - - switch(fr->frametype) { - case AST_FRAME_DTMF: - res = fr->subclass; - snprintf(exten, sizeof(exten), "%c", res); - if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) { - ast_frfree(fr); - return res; - } - break; - case AST_FRAME_CONTROL: - switch(fr->subclass) { - case AST_CONTROL_HANGUP: - ast_frfree(fr); - return -1; - case AST_CONTROL_RINGING: - case AST_CONTROL_ANSWER: - /* Unimportant */ - break; - default: - ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass); - } - } - /* Ignore */ - ast_frfree(fr); - } - ast_sched_runq(c->sched); - } - return (c->_softhangup ? -1 : 0); + + if (!context) + context = c->context; + return waitstream_core(c, NULL, NULL, NULL, 0, + -1, -1, context); } static int show_file_formats(int fd, int argc, char *argv[]) @@ -1272,7 +1139,6 @@ static int show_file_formats(int fd, int argc, char *argv[]) return RESULT_SUCCESS; #undef FORMAT #undef FORMAT2 - } struct ast_cli_entry show_file = |