[asterisk-dev] language-specific sound directories

Luigi Rizzo rizzo at icir.org
Fri Jan 27 11:44:02 MST 2006


On Fri, Jan 27, 2006 at 12:21:21PM -0600, Kevin P. Fleming wrote:
> Luigi Rizzo wrote:
> > looking at file.c it seems that language-specific
...
> without having to split apart the path that was provided.
> 
> > and so on. I would have expected separate trees for
> > each language i.e.
> > 
> > 	.../sounds/en/*		everything in english
> > 	.../sounds/it/*		everything in italian
> > 
> > which would be way easier to manage if you have to install/delete
> > third-party voice files.
> 
> Yes, it would, but it requires the path supplied to the file open 
> functions to be split into two pieces (or have some sort of substitution 
> mechanism to control where the language code gets inserted).

yes... anyways, see the attached file.c that replaces the one in -head
and implements both behaviours under control of a config variable.
(I am going to post on mantis when i have polished it a bit more)

This version moves the file lookup code (to insert the prefix)
into a single function (it was in 3 places befora, and the
3 implementations were slightly different) and adds a significant
amount of comments and simplifications in the code.

There are still redundancies to remove (ast_waitstream*() variants should
be collapsed to one) but one thing at a time...

> Keep in mind that sound file are _not_ required to be kept under the 
> ../sounds directory; as it works today, I can specify 
> Playback(/long/path/to/sound/file/in/my/directory) and the language code 
> still gets added to the end when needed.

yes but it's not that you have total freedom - you still need to place
the language-specicif files in places consistent with the (hardwired)
rule. But i wonder, isn't there a $LANGUAGE variable or something
similar that you can use to build the pathname ?

cheers
luigi
-------------- next part --------------
/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 1999 - 2006, Digium, Inc.
 *
 * Mark Spencer <markster at digium.com>
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

/*! \file
 *
 * \brief Generic File Format Support.
 *
 * \author Mark Spencer <markster at digium.com> 
 */

#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: 7912 $")

#include "asterisk/frame.h"
#include "asterisk/file.h"
#include "asterisk/cli.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/sched.h"
#include "asterisk/options.h"
#include "asterisk/translate.h"
#include "asterisk/utils.h"
#include "asterisk/lock.h"
#include "asterisk/app.h"
#include "asterisk/pbx.h"
#include "asterisk/linkedlists.h"

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 *, long offset, int whence);
	/*! trunc file to current position */
	int (*trunc)(struct ast_filestream *fs);
	/*! tell current position */
	long (*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 classical 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 *, long sample_offset, int whence),
						int (*trunc)(struct ast_filestream *),
						long (*tell)(struct ast_filestream *),
						struct ast_frame * (*read)(struct ast_filestream *, int *whennext),
						void (*close)(struct ast_filestream *),
						char * (*getcomment)(struct ast_filestream *))
{
	struct ast_format *tmp;
	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)) {
			AST_LIST_UNLOCK(&formats);
			ast_log(LOG_WARNING, "Tried to register '%s' format, already registered\n", name);
			return -1;
		}
	}
	tmp = malloc(sizeof(struct ast_format));
	if (!tmp) {
		ast_log(LOG_WARNING, "Out of memory\n");
		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;
	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);
	return 0;
}

int ast_format_unregister(const char *name)
{
	struct ast_format *tmp;
	int res = -1;

	if (AST_LIST_LOCK(&formats)) {
		ast_log(LOG_WARNING, "Unable to lock format list\n");
		return -1;
	}
	AST_LIST_TRAVERSE_SAFE_BEGIN(&formats, tmp, list) {
		if (!strcasecmp(name, tmp->name)) {
			AST_LIST_REMOVE_CURRENT(&formats, list);
			free(tmp);
			res = 0;
		}
	}
	AST_LIST_TRAVERSE_SAFE_END
	AST_LIST_UNLOCK(&formats);

	if (tmp) {
		if (option_verbose > 1)
				ast_verbose( VERBOSE_PREFIX_2 "Unregistered format %s\n", name);
	} else
		ast_log(LOG_WARNING, "Tried to unregister format %s, already unregistered\n", name);

	return res;
}

int ast_stopstream(struct ast_channel *tmp)
{
	/* Stop a running stream if there is one */
	if (tmp->stream) {
		ast_closestream(tmp->stream);
		if (tmp->oldwriteformat && ast_set_write_format(tmp, tmp->oldwriteformat))
			ast_log(LOG_WARNING, "Unable to restore format back to %d\n", tmp->oldwriteformat);
	}
	return 0;
}

int ast_writestream(struct ast_filestream *fs, struct ast_frame *f)
{
	struct ast_frame *trf;
	int res = -1;
	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... */
			if (!fs->vfs && fs->filename) {
				const char *type = ast_getformatname(f->subclass & ~0x1);
				fs->vfs = ast_writefile(fs->filename, type, NULL, fs->flags, 0, fs->mode);
				ast_log(LOG_DEBUG, "Opened video output file\n");
			}
			if (fs->vfs)
				return ast_writestream(fs->vfs, f);
			/* Ignore */
			return 0;				
		} else {
			/* Might / might not have mark set */
			alt = 1;
		}
	} else if (f->frametype != AST_FRAME_VOICE) {
		ast_log(LOG_WARNING, "Tried to write non-voice frame\n");
		return -1;
	}
	if (((fs->fmt->format | alt) & f->subclass) == f->subclass) {
		res =  fs->fmt->write(fs, f);
		if (res < 0) 
			ast_log(LOG_WARNING, "Natural write failed\n");
		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)) {
			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));
		else {
			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) {
				res = fs->fmt->write(fs, trf);
				if (res) 
					ast_log(LOG_WARNING, "Translated frame write failed\n");
			} else
				res = 0;
		}
		return res;
	}
}

static int copy(const char *infile, const char *outfile)
{
	int ifd, ofd, len;
	char buf[4096];

	if ((ifd = open(infile, O_RDONLY)) < 0) {
		ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
		return -1;
	}
	if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0) {
		ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
		close(ifd);
		return -1;
	}
	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));
			break;
		}
		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;
		}
	}
	close(ifd);
	close(ofd);
	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'.
 */
static char *build_filename(const char *filename, const char *ext)
{
	char *fn = NULL;

	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 tmp[256];
	char *stringp = tmp, *ext;

	ast_copy_string(tmp, exts, sizeof(tmp));
	while ((ext = strsep(&stringp, "|"))) {
		if (!strcmp(ext, type))
			return 1;
	}

	return 0;
}

#define ACTION_EXISTS 1	/* return matching format if file exists, 0 otherwise */
#define ACTION_DELETE 2	/* delete file, return 0 on success, -1 on error */
#define ACTION_RENAME 3	/* rename file. return 0 on success, -1 on error */
#define ACTION_OPEN   4
#define ACTION_COPY   5	/* copy file. return 0 on success, -1 on error */

/*!
 * \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, int action)
{
	struct ast_format *f;
	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) {
		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 = f->open(bfile);
				if (!s) {
					fclose(bfile);
					free(fn);
					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);
	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);
			}
		}
ast_log(LOG_WARNING, "looking for %s\n", buf);
		res = ast_filehelper(buf, NULL, fmt, ACTION_EXISTS);
		if (res > 0)
			break;	/* found format */
		if (langlen == 0)
			break;	/* no more formats */
		else if (preflang[langlen] == '_') /* we are on the local suffix */
			langlen = 0;	/* try again with no language */
		else {
			langlen = (c = strchr(preflang, '_')) ? c - preflang : 0;
		}
	}
ast_log(LOG_WARNING, "return %d <%s>\n", res, buf);
	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);
}

struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char *filename, const char *preflang, int asis)
{
	/* 
	 * 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 */
		ast_stopstream(chan);
		if (chan->generator)
			ast_deactivate_generator(chan);
	}
	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;
	}
	chan->oldwriteformat = chan->writeformat;
	/* Set the channel to a format we can work with */
	res = ast_set_write_format(chan, fmts);
ast_log(LOG_WARNING, "about to open %s in format %x\n", buf, fmts);
 	res = ast_filehelper(buf, chan, NULL, ACTION_OPEN);
	if (res >= 0)
		return chan->stream;
	return NULL;
}

struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *filename, const char *preflang)
{
	/* As above, but for video. But here we don't have translators
	 * so we must enforce a format.
	 */
	unsigned int format;
	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 ( fileexists_core(filename, fmt, preflang, buf, buflen) < 1)	/* no valid format */
			continue;
	 	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);
	}
	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;
	struct ast_frame *fr;
	int whennext = 0;

	while(!whennext) {
		fr = s->fmt->read(s, &whennext);
		if (fr) {
			if (ast_write(s->owner, 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);
#endif			
			return 0;
		}
	}
	if (whennext != s->lasttimeout) {
#ifdef ZAPTEL_OPTIMIZATIONS
		if (s->owner->timingfd > -1)
			ast_settimeout(s->owner, whennext, ast_readaudio_callback, s);
		else
#endif		
			s->owner->streamid = ast_sched_add(s->owner->sched, whennext/8, ast_readaudio_callback, s);
		s->lasttimeout = whennext;
		return 0;
	}
	return 1;
}

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)) {
				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;
		}
	}
	if (whennext != s->lasttimeout) {
		s->owner->vstreamid = ast_sched_add(s->owner->sched, whennext/8, ast_readvideo_callback, s);
		s->lasttimeout = whennext;
		return 0;
	}
	return 1;
}

int ast_applystream(struct ast_channel *chan, struct ast_filestream *s)
{
	s->owner = chan;
	return 0;
}

int ast_playstream(struct ast_filestream *s)
{
	if (s->fmt->format < AST_FORMAT_MAX_AUDIO)
		ast_readaudio_callback(s);
	else
		ast_readvideo_callback(s);
	return 0;
}

int ast_seekstream(struct ast_filestream *fs, long sample_offset, int whence)
{
	return fs->fmt->seek(fs, sample_offset, whence);
}

int ast_truncstream(struct ast_filestream *fs)
{
	return fs->fmt->trunc(fs);
}

long ast_tellstream(struct ast_filestream *fs)
{
	return fs->fmt->tell(fs);
}

int ast_stream_fastforward(struct ast_filestream *fs, long ms)
{
	/* I think this is right, 8000 samples per second, 1000 ms a second so 8
	 * samples per ms  */
	long samples = ms * 8;
	return ast_seekstream(fs, samples, SEEK_CUR);
}

int ast_stream_rewind(struct ast_filestream *fs, long ms)
{
	long samples = ms * 8;
	samples = samples * -1;
	return ast_seekstream(fs, samples, SEEK_CUR);
}

int ast_closestream(struct ast_filestream *f)
{
	char *cmd = NULL;
	size_t size = 0;
	/* Stop a running stream if there is one */
	if (f->owner) {
		if (f->fmt->format < AST_FORMAT_MAX_AUDIO) {
			f->owner->stream = NULL;
			if (f->owner->streamid > -1)
				ast_sched_del(f->owner->sched, f->owner->streamid);
			f->owner->streamid = -1;
#ifdef ZAPTEL_OPTIMIZATIONS
			ast_settimeout(f->owner, 0, NULL, NULL);
#endif			
		} else {
			f->owner->vstream = NULL;
			if (f->owner->vstreamid > -1)
				ast_sched_del(f->owner->sched, f->owner->vstreamid);
			f->owner->vstreamid = -1;
		}
	}
	/* destroy the translator on exit */
	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;
			cmd = alloca(size);
			memset(cmd,0,size);
			snprintf(cmd,size,"/bin/mv -f %s %s",f->filename,f->realfilename);
			ast_safe_system(cmd);
	}

	if (f->filename) {
		free(f->filename);
		f->filename = NULL;
	}
	if (f->realfilename) {
		free(f->realfilename);
		f->realfilename = NULL;
	}
	f->fmt->close(f);
	if (f->vfs) {
		ast_closestream(f->vfs);
		f->vfs = NULL;
	}
	return 0;
}


/*
 * Try preflang, preflang with stripped suffix, or NULL.
 * The language goes just before the last component (ouch! XXX)
 */
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
{
	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)
{
	return ast_filehelper(filename, NULL, fmt, ACTION_DELETE);
}

int ast_filerename(const char *filename, const char *filename2, const char *fmt)
{
	return ast_filehelper(filename, filename2, fmt, ACTION_RENAME);
}

int ast_filecopy(const char *filename, const char *filename2, const char *fmt)
{
	return ast_filehelper(filename, filename2, fmt, ACTION_COPY);
}

int ast_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
{
	struct ast_filestream *fs;
	struct ast_filestream *vfs=NULL;
	char fmt[256];

	fs = ast_openstream(chan, filename, preflang);
	if (fs)
		vfs = ast_openvstream(chan, filename, preflang);
	if (vfs)
		ast_log(LOG_DEBUG, "Ooh, found a video stream, too, format %s\n", ast_getformatname(vfs->fmt->format));
	if (fs){
		if (ast_applystream(chan, fs))
			return -1;
		if (vfs && ast_applystream(chan, vfs))
			return -1;
		if (ast_playstream(fs))
			return -1;
		if (vfs && ast_playstream(vfs))
			return -1;
#if 1
		if (option_verbose > 2)
			ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (language '%s')\n", filename, preflang ? preflang : "default");
#endif
		return 0;
	}
	ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n", filename, ast_getformatname_multiple(fmt, sizeof(fmt), chan->nativeformats), strerror(errno));
	return -1;
}

struct ast_filestream *ast_readfile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode)
{
	FILE *bfile;
	struct ast_format *f;
	struct ast_filestream *fs = NULL;
	char *fn;

	if (AST_LIST_LOCK(&formats)) {
		ast_log(LOG_WARNING, "Unable to lock format list\n");
		return NULL;
	}

	AST_LIST_TRAVERSE(&formats, f, list) {
		if (fs)
			break;

		if (!exts_compare(f->exts, type))
			continue;

		fn = build_filename(filename, type);
		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);
	}

	AST_LIST_UNLOCK(&formats);
	if (!fs) 
		ast_log(LOG_WARNING, "No such format '%s'\n", type);

	return fs;
}

struct ast_filestream *ast_writefile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode)
{
	int fd, myflags = 0;
	/* compiler claims this variable can be used before initialization... */
	FILE *bfile = NULL;
	struct ast_format *f;
	struct ast_filestream *fs = NULL;
	char *fn, *orig_fn = NULL;
	char *buf = NULL;
	size_t size = 0;

	if (AST_LIST_LOCK(&formats)) {
		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) { 
		/* We really can't use O_APPEND as it will break WAV header updates */
		flags &= ~O_APPEND;
	} else {
		myflags = O_TRUNC;
	}
	
	myflags |= O_WRONLY | O_CREAT;

	AST_LIST_TRAVERSE(&formats, f, list) {
		if (fs)
			break;

		if (!exts_compare(f->exts, type))
			continue;

		fn = build_filename(filename, type);
		fd = open(fn, flags | myflags, mode);
		if (fd > -1) {
			/* fdopen() the resulting file stream */
			bfile = fdopen(fd, ((flags | myflags) & O_RDWR) ? "w+" : "w");
			if (!bfile) {
				ast_log(LOG_WARNING, "Whoa, fdopen failed: %s!\n", strerror(errno));
				close(fd);
				fd = -1;
			}
		}
		
		if (ast_opt_cache_record_files && (fd > -1)) {
			char *c;

			fclose(bfile);
			/*
			  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.
			*/
			orig_fn = ast_strdupa(fn);
			for (c = fn; *c; c++)
				if (*c == '/')
					*c = '_';

			size = strlen(fn) + strlen(record_cache_dir) + 2;
			buf = alloca(size);
			strcpy(buf, record_cache_dir);
			strcat(buf, "/");
			strcat(buf, fn);
			free(fn);
			fn = buf;
			fd = open(fn, flags | myflags, mode);
			if (fd > -1) {
				/* fdopen() the resulting file stream */
				bfile = fdopen(fd, ((flags | myflags) & O_RDWR) ? "w+" : "w");
				if (!bfile) {
					ast_log(LOG_WARNING, "Whoa, fdopen failed: %s!\n", strerror(errno));
					close(fd);
					fd = -1;
				}
			}
		}
		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 {
				ast_log(LOG_WARNING, "Unable to rewrite %s\n", fn);
				close(fd);
				if (orig_fn) {
					unlink(fn);
					unlink(orig_fn);
				}
			}
		} else if (errno != EEXIST) {
			ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno));
			if (orig_fn)
				unlink(orig_fn);
		}
		/* if buf != NULL then fn is already free and pointing to it */
		if (!buf)
			free(fn);
	}

	AST_LIST_UNLOCK(&formats);
	if (!fs)
		ast_log(LOG_WARNING, "No such format '%s'\n", type);

	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)
{
	int res;
	struct ast_frame *fr;

	if (!breakon)
			breakon = "";
	if (!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 = "";
	
	while(c->stream) {
		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
				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:
					/* Unimportant */
					break;
				default:
					ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass);
				}
			case AST_FRAME_VOICE:
				/* Write audio if appropriate */
				if (audiofd > -1)
					write(audiofd, fr->data, fr->datalen);
			}
			/* Ignore */
			ast_frfree(fr);
		}
		ast_sched_runq(c->sched);
	}
	return (c->_softhangup ? -1 : 0);
}

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);
}

static int show_file_formats(int fd, int argc, char *argv[])
{
#define FORMAT "%-10s %-10s %-20s\n"
#define FORMAT2 "%-10s %-10s %-20s\n"
	struct ast_format *f;
	int count_fmt = 0;

	if (argc != 3)
		return RESULT_SHOWUSAGE;
	ast_cli(fd, FORMAT, "Format", "Name", "Extensions");
	        
	if (AST_LIST_LOCK(&formats)) {
		ast_log(LOG_WARNING, "Unable to lock format list\n");
		return -1;
	}

	AST_LIST_TRAVERSE(&formats, f, list) {
		ast_cli(fd, FORMAT2, ast_getformatname(f->format), f->name, f->exts);
		count_fmt++;
	}
	AST_LIST_UNLOCK(&formats);
	ast_cli(fd, "%d file formats registered.\n", count_fmt);
	return RESULT_SUCCESS;
#undef FORMAT
#undef FORMAT2
	
}

struct ast_cli_entry show_file =
{
	{ "show", "file", "formats" },
	show_file_formats,
	"Displays file formats",
	"Usage: show file formats\n"
	"       displays currently registered file formats (if any)\n"
};

int ast_file_init(void)
{
	ast_cli_register(&show_file);
	return 0;
}


More information about the asterisk-dev mailing list