ARI - channel recording support
This patch is the first step in adding recording support to the Asterisk REST Interface. Recordings are stored in /var/spool/recording. Since recordings may be destructive (overwriting existing files), the API rejects attempts to escape the recording directory (avoiding issues if someone attempts to record to ../../lib/sounds/greeting, for example). (closes issue ASTERISK-21594) (closes issue ASTERISK-21581) Review: https://reviewboard.asterisk.org/r/2612/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393550 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
parent
c4adaf9106
commit
a75fd32212
3
Makefile
3
Makefile
|
@ -536,7 +536,8 @@ OLDHEADERS=$(filter-out $(NEWHEADERS) $(notdir $(DESTDIR)$(ASTHEADERDIR)),$(notd
|
|||
INSTALLDIRS="$(ASTLIBDIR)" "$(ASTMODDIR)" "$(ASTSBINDIR)" "$(ASTETCDIR)" "$(ASTVARRUNDIR)" \
|
||||
"$(ASTSPOOLDIR)" "$(ASTSPOOLDIR)/dictate" "$(ASTSPOOLDIR)/meetme" \
|
||||
"$(ASTSPOOLDIR)/monitor" "$(ASTSPOOLDIR)/system" "$(ASTSPOOLDIR)/tmp" \
|
||||
"$(ASTSPOOLDIR)/voicemail" "$(ASTHEADERDIR)" "$(ASTHEADERDIR)/doxygen" \
|
||||
"$(ASTSPOOLDIR)/voicemail" "$(ASTSPOOLDIR)/recording" \
|
||||
"$(ASTHEADERDIR)" "$(ASTHEADERDIR)/doxygen" \
|
||||
"$(ASTLOGDIR)" "$(ASTLOGDIR)/cdr-csv" "$(ASTLOGDIR)/cdr-custom" \
|
||||
"$(ASTLOGDIR)/cel-custom" "$(ASTDATADIR)" "$(ASTDATADIR)/documentation" \
|
||||
"$(ASTDATADIR)/documentation/thirdparty" "$(ASTDATADIR)/firmware" \
|
||||
|
|
|
@ -1674,7 +1674,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
|
|||
ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
|
||||
if (ast_test_flag(vmu, MVM_OPERATOR))
|
||||
canceldtmf = "0";
|
||||
cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
|
||||
cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
|
||||
if (record_gain)
|
||||
ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
|
||||
if (cmd == -1) /* User has hung up, no options to give */
|
||||
|
|
|
@ -14684,7 +14684,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
|
|||
ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
|
||||
if (ast_test_flag(vmu, VM_OPERATOR))
|
||||
canceldtmf = "0";
|
||||
cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, sound_duration, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf);
|
||||
cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, sound_duration, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
|
||||
if (strchr(canceldtmf, cmd)) {
|
||||
/* need this flag here to distinguish between pressing '0' during message recording or after */
|
||||
canceleddtmf = 1;
|
||||
|
|
|
@ -690,9 +690,23 @@ int ast_control_streamfile_w_cb(struct ast_channel *chan,
|
|||
/*! \brief Play a stream and wait for a digit, returning the digit that was pressed */
|
||||
int ast_play_and_wait(struct ast_channel *chan, const char *fn);
|
||||
|
||||
/*!
|
||||
* Possible actions to take if a recording already exists
|
||||
* \since 12
|
||||
*/
|
||||
enum ast_record_if_exists {
|
||||
/*! Fail the recording. */
|
||||
AST_RECORD_IF_EXISTS_FAIL,
|
||||
/*! Overwrite the existing recording. */
|
||||
AST_RECORD_IF_EXISTS_OVERWRITE,
|
||||
/*! Append to the existing recording. */
|
||||
AST_RECORD_IF_EXISTS_APPEND,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Record a file based on input from a channel
|
||||
* This function will play "auth-thankyou" upon successful recording.
|
||||
* This function will play "auth-thankyou" upon successful recording if
|
||||
* skip_confirmation_sound is false.
|
||||
*
|
||||
* \param chan the channel being recorded
|
||||
* \param playfile Filename of sound to play before recording begins
|
||||
|
@ -706,13 +720,15 @@ int ast_play_and_wait(struct ast_channel *chan, const char *fn);
|
|||
* \param path Optional filesystem path to unlock
|
||||
* \param acceptdtmf Character of DTMF to end and accept the recording
|
||||
* \param canceldtmf Character of DTMF to end and cancel the recording
|
||||
* \param skip_confirmation_sound If true, don't play auth-thankyou at end. Nice for custom recording prompts in apps.
|
||||
* \param if_exists Action to take if recording already exists.
|
||||
*
|
||||
* \retval -1 failure or hangup
|
||||
* \retval 'S' Recording ended from silence timeout
|
||||
* \retval 't' Recording ended from the message exceeding the maximum duration
|
||||
* \retval dtmfchar Recording ended via the return value's DTMF character for either cancel or accept.
|
||||
*/
|
||||
int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf);
|
||||
int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists);
|
||||
|
||||
/*!
|
||||
* \brief Record a file based on input from a channel. Use default accept and cancel DTMF.
|
||||
|
|
|
@ -1603,6 +1603,18 @@ void ast_channel_setwhentohangup_tv(struct ast_channel *chan, struct timeval off
|
|||
*/
|
||||
int ast_answer(struct ast_channel *chan);
|
||||
|
||||
/*!
|
||||
* \brief Answer a channel, if it's not already answered.
|
||||
*
|
||||
* \param chan channel to answer
|
||||
*
|
||||
* \details See ast_answer()
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval non-zero on failure
|
||||
*/
|
||||
int ast_auto_answer(struct ast_channel *chan);
|
||||
|
||||
/*!
|
||||
* \brief Answer a channel
|
||||
*
|
||||
|
|
|
@ -64,8 +64,8 @@ enum ast_waitstream_fr_cb_values {
|
|||
*/
|
||||
typedef void (ast_waitstream_fr_cb)(struct ast_channel *chan, long ms, enum ast_waitstream_fr_cb_values val);
|
||||
|
||||
/*!
|
||||
* \brief Streams a file
|
||||
/*!
|
||||
* \brief Streams a file
|
||||
* \param c channel to stream the file to
|
||||
* \param filename the name of the file you wish to stream, minus the extension
|
||||
* \param preflang the preferred language you wish to have the file streamed to you in
|
||||
|
@ -86,12 +86,12 @@ int ast_streamfile(struct ast_channel *c, const char *filename, const char *pref
|
|||
*/
|
||||
int ast_stream_and_wait(struct ast_channel *chan, const char *file, const char *digits);
|
||||
|
||||
/*!
|
||||
* \brief Stops a stream
|
||||
/*!
|
||||
* \brief Stops a stream
|
||||
*
|
||||
* \param c The channel you wish to stop playback on
|
||||
*
|
||||
* Stop playback of a stream
|
||||
* Stop playback of a stream
|
||||
*
|
||||
* \retval 0 always
|
||||
*
|
||||
|
|
|
@ -23,6 +23,7 @@ extern const char *ast_config_AST_CONFIG_FILE;
|
|||
extern const char *ast_config_AST_MODULE_DIR;
|
||||
extern const char *ast_config_AST_SPOOL_DIR;
|
||||
extern const char *ast_config_AST_MONITOR_DIR;
|
||||
extern const char *ast_config_AST_RECORDING_DIR;
|
||||
extern const char *ast_config_AST_VAR_DIR;
|
||||
extern const char *ast_config_AST_DATA_DIR;
|
||||
extern const char *ast_config_AST_LOG_DIR;
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* David M. Lee, II <dlee@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.
|
||||
*/
|
||||
|
||||
#ifndef _ASTERISK_STASIS_APP_RECORDING_H
|
||||
#define _ASTERISK_STASIS_APP_RECORDING_H
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Stasis Application Recording API. See \ref res_stasis "Stasis
|
||||
* Application API" for detailed documentation.
|
||||
*
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
* \since 12
|
||||
*/
|
||||
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/stasis_app.h"
|
||||
|
||||
/*! Opaque struct for handling the recording of media to a file. */
|
||||
struct stasis_app_recording;
|
||||
|
||||
/*! State of a recording operation */
|
||||
enum stasis_app_recording_state {
|
||||
/*! The recording has not started yet */
|
||||
STASIS_APP_RECORDING_STATE_QUEUED,
|
||||
/*! The media is currently recording */
|
||||
STASIS_APP_RECORDING_STATE_RECORDING,
|
||||
/*! The media is currently paused */
|
||||
STASIS_APP_RECORDING_STATE_PAUSED,
|
||||
/*! The media has stopped recording */
|
||||
STASIS_APP_RECORDING_STATE_COMPLETE,
|
||||
/*! The media has stopped playing */
|
||||
STASIS_APP_RECORDING_STATE_FAILED,
|
||||
};
|
||||
|
||||
/*! Valid operation for controlling a recording. */
|
||||
enum stasis_app_recording_media_operation {
|
||||
/*! Stop the recording operation. */
|
||||
STASIS_APP_RECORDING_STOP,
|
||||
};
|
||||
|
||||
#define STASIS_APP_RECORDING_TERMINATE_INVALID 0
|
||||
#define STASIS_APP_RECORDING_TERMINATE_NONE -1
|
||||
#define STASIS_APP_RECORDING_TERMINATE_ANY -2
|
||||
|
||||
struct stasis_app_recording_options {
|
||||
AST_DECLARE_STRING_FIELDS(
|
||||
AST_STRING_FIELD(name); /*!< name Name of the recording. */
|
||||
AST_STRING_FIELD(format); /*!< Format to be recorded (wav, gsm, etc.) */
|
||||
);
|
||||
/*! Number of seconds of silence before ending the recording. */
|
||||
int max_silence_seconds;
|
||||
/*! Maximum recording duration. 0 for no maximum. */
|
||||
int max_duration_seconds;
|
||||
/*! Which DTMF to use to terminate the recording
|
||||
* \c STASIS_APP_RECORDING_TERMINATE_NONE to terminate only on hangup
|
||||
* \c STASIS_APP_RECORDING_TERMINATE_ANY to terminate on any DTMF
|
||||
*/
|
||||
char terminate_on;
|
||||
/*! How to handle recording when a file already exists */
|
||||
enum ast_record_if_exists if_exists;
|
||||
/*! If true, a beep is played at the start of recording */
|
||||
int beep:1;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Allocate a recording options object.
|
||||
*
|
||||
* Clean up with ao2_cleanup().
|
||||
*
|
||||
* \param name Name of the recording.
|
||||
* \param format Format to record in.
|
||||
* \return Newly allocated options object.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
struct stasis_app_recording_options *stasis_app_recording_options_create(
|
||||
const char *name, const char *format);
|
||||
|
||||
/*!
|
||||
* \brief Parse a string into the recording termination enum.
|
||||
*
|
||||
* \param str String to parse.
|
||||
* \return DTMF value to terminate on.
|
||||
* \return \c STASIS_APP_RECORDING_TERMINATE_NONE to not terminate on DTMF.
|
||||
* \return \c STASIS_APP_RECORDING_TERMINATE_ANY to terminate on any DTMF.
|
||||
* \return \c STASIS_APP_RECORDING_TERMINATE_INVALID if input was invalid.
|
||||
*/
|
||||
char stasis_app_recording_termination_parse(const char *str);
|
||||
|
||||
/*!
|
||||
* \brief Parse a string into the if_exists enum.
|
||||
*
|
||||
* \param str String to parse.
|
||||
* \return How to handle an existing file.
|
||||
* \return -1 on error.
|
||||
*/
|
||||
enum ast_record_if_exists stasis_app_recording_if_exists_parse(
|
||||
const char *str);
|
||||
|
||||
/*!
|
||||
* \brief Record media from a channel.
|
||||
*
|
||||
* A reference to the \a options object may be kept, so it MUST NOT be modified
|
||||
* after calling this function.
|
||||
*
|
||||
* On error, \c errno is set to indicate the failure reason.
|
||||
* - \c EINVAL: Invalid input.
|
||||
* - \c EEXIST: A recording with that name is in session.
|
||||
* - \c ENOMEM: Out of memory.
|
||||
*
|
||||
* \param control Control for \c res_stasis.
|
||||
* \param options Recording options.
|
||||
* \return Recording control object.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
struct stasis_app_recording *stasis_app_control_record(
|
||||
struct stasis_app_control *control,
|
||||
struct stasis_app_recording_options *options);
|
||||
|
||||
/*!
|
||||
* \brief Gets the current state of a recording operation.
|
||||
*
|
||||
* \param recording Recording control object.
|
||||
* \return The state of the \a recording object.
|
||||
*/
|
||||
enum stasis_app_recording_state stasis_app_recording_get_state(
|
||||
struct stasis_app_recording *recording);
|
||||
|
||||
/*!
|
||||
* \brief Gets the unique name of a recording object.
|
||||
*
|
||||
* \param recording Recording control object.
|
||||
* \return \a recording's name.
|
||||
* \return \c NULL if \a recording ic \c NULL
|
||||
*/
|
||||
const char *stasis_app_recording_get_name(
|
||||
struct stasis_app_recording *recording);
|
||||
|
||||
/*!
|
||||
* \brief Finds the recording object with the given name.
|
||||
*
|
||||
* \param name Name of the recording object to find.
|
||||
* \return Associated \ref stasis_app_recording object.
|
||||
* \return \c NULL if \a name not found.
|
||||
*/
|
||||
struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name);
|
||||
|
||||
/*!
|
||||
* \brief Construct a JSON model of a recording.
|
||||
*
|
||||
* \param recording Recording to conver.
|
||||
* \return JSON model.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
struct ast_json *stasis_app_recording_to_json(
|
||||
const struct stasis_app_recording *recording);
|
||||
|
||||
/*!
|
||||
* \brief Possible results from a recording operation.
|
||||
*/
|
||||
enum stasis_app_recording_oper_results {
|
||||
/*! Operation completed successfully. */
|
||||
STASIS_APP_RECORDING_OPER_OK,
|
||||
/*! Operation failed. */
|
||||
STASIS_APP_RECORDING_OPER_FAILED,
|
||||
/*! Operation failed b/c recording is not in session. */
|
||||
STASIS_APP_RECORDING_OPER_NOT_RECORDING,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Controls the media for a given recording operation.
|
||||
*
|
||||
* \param recording Recording control object.
|
||||
* \param control Media control operation.
|
||||
* \return \c STASIS_APP_RECORDING_OPER_OK on success.
|
||||
* \return \ref stasis_app_recording_oper_results indicating failure.
|
||||
*/
|
||||
enum stasis_app_recording_oper_results stasis_app_recording_operation(
|
||||
struct stasis_app_recording *recording,
|
||||
enum stasis_app_recording_media_operation operation);
|
||||
|
||||
/*!
|
||||
* \brief Message type for recording updates. The data is an
|
||||
* \ref ast_channel_blob.
|
||||
*/
|
||||
struct stasis_message_type *stasis_app_recording_snapshot_type(void);
|
||||
|
||||
#endif /* _ASTERISK_STASIS_APP_RECORDING_H */
|
|
@ -718,6 +718,19 @@ void ast_enable_packet_fragmentation(int sock);
|
|||
*/
|
||||
int ast_mkdir(const char *path, int mode);
|
||||
|
||||
/*!
|
||||
* \brief Recursively create directory path, but only if it resolves within
|
||||
* the given \a base_path.
|
||||
*
|
||||
* If \a base_path does not exist, it will not be created and this function
|
||||
* returns \c EPERM.
|
||||
*
|
||||
* \param path The directory path to create
|
||||
* \param mode The permissions with which to try to create the directory
|
||||
* \return 0 on success or an error code otherwise
|
||||
*/
|
||||
int ast_safe_mkdir(const char *base_path, const char *path, int mode);
|
||||
|
||||
#define ARRAY_LEN(a) (size_t) (sizeof(a) / sizeof(0[a]))
|
||||
|
||||
|
||||
|
|
27
main/app.c
27
main/app.c
|
@ -1169,7 +1169,7 @@ static int global_maxsilence = 0;
|
|||
* \retval 't' Recording ended from the message exceeding the maximum duration, or via DTMF in prepend mode
|
||||
* \retval dtmfchar Recording ended via the return value's DTMF character for either cancel or accept.
|
||||
*/
|
||||
static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound)
|
||||
static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
|
||||
{
|
||||
int d = 0;
|
||||
char *fmts;
|
||||
|
@ -1186,6 +1186,21 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
|
|||
struct ast_format rfmt;
|
||||
struct ast_silence_generator *silgen = NULL;
|
||||
char prependfile[PATH_MAX];
|
||||
int ioflags; /* IO flags for writing output file */
|
||||
|
||||
ioflags = O_CREAT|O_WRONLY;
|
||||
|
||||
switch (if_exists) {
|
||||
case AST_RECORD_IF_EXISTS_FAIL:
|
||||
ioflags |= O_EXCL;
|
||||
break;
|
||||
case AST_RECORD_IF_EXISTS_OVERWRITE:
|
||||
ioflags |= O_TRUNC;
|
||||
break;
|
||||
case AST_RECORD_IF_EXISTS_APPEND:
|
||||
ioflags |= O_APPEND;
|
||||
break;
|
||||
}
|
||||
|
||||
ast_format_clear(&rfmt);
|
||||
if (silencethreshold < 0) {
|
||||
|
@ -1239,7 +1254,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
|
|||
|
||||
end = start = time(NULL); /* pre-initialize end to be same as start in case we never get into loop */
|
||||
for (x = 0; x < fmtcnt; x++) {
|
||||
others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, O_TRUNC, 0, AST_FILE_MODE);
|
||||
others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, ioflags, 0, AST_FILE_MODE);
|
||||
ast_verb(3, "x=%d, open writing: %s format: %s, %p\n", x, prepend ? prependfile : recordfile, sfmt[x], others[x]);
|
||||
|
||||
if (!others[x]) {
|
||||
|
@ -1477,19 +1492,19 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
|
|||
static const char default_acceptdtmf[] = "#";
|
||||
static const char default_canceldtmf[] = "";
|
||||
|
||||
int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf)
|
||||
int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
|
||||
{
|
||||
return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), 0);
|
||||
return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), skip_confirmation_sound, if_exists);
|
||||
}
|
||||
|
||||
int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path)
|
||||
{
|
||||
return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf, 0);
|
||||
return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
|
||||
}
|
||||
|
||||
int ast_play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence)
|
||||
{
|
||||
return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf, 1);
|
||||
return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf, 1, AST_RECORD_IF_EXISTS_OVERWRITE);
|
||||
}
|
||||
|
||||
/* Channel group core functions */
|
||||
|
|
|
@ -373,6 +373,7 @@ struct _cfg_paths {
|
|||
char module_dir[PATH_MAX];
|
||||
char spool_dir[PATH_MAX];
|
||||
char monitor_dir[PATH_MAX];
|
||||
char recording_dir[PATH_MAX];
|
||||
char var_dir[PATH_MAX];
|
||||
char data_dir[PATH_MAX];
|
||||
char log_dir[PATH_MAX];
|
||||
|
@ -397,6 +398,7 @@ const char *ast_config_AST_CONFIG_FILE = cfg_paths.config_file;
|
|||
const char *ast_config_AST_MODULE_DIR = cfg_paths.module_dir;
|
||||
const char *ast_config_AST_SPOOL_DIR = cfg_paths.spool_dir;
|
||||
const char *ast_config_AST_MONITOR_DIR = cfg_paths.monitor_dir;
|
||||
const char *ast_config_AST_RECORDING_DIR = cfg_paths.recording_dir;
|
||||
const char *ast_config_AST_VAR_DIR = cfg_paths.var_dir;
|
||||
const char *ast_config_AST_DATA_DIR = cfg_paths.data_dir;
|
||||
const char *ast_config_AST_LOG_DIR = cfg_paths.log_dir;
|
||||
|
@ -3306,6 +3308,7 @@ static void ast_readconfig(void)
|
|||
ast_copy_string(cfg_paths.spool_dir, DEFAULT_SPOOL_DIR, sizeof(cfg_paths.spool_dir));
|
||||
ast_copy_string(cfg_paths.module_dir, DEFAULT_MODULE_DIR, sizeof(cfg_paths.module_dir));
|
||||
snprintf(cfg_paths.monitor_dir, sizeof(cfg_paths.monitor_dir), "%s/monitor", cfg_paths.spool_dir);
|
||||
snprintf(cfg_paths.recording_dir, sizeof(cfg_paths.recording_dir), "%s/recording", cfg_paths.spool_dir);
|
||||
ast_copy_string(cfg_paths.var_dir, DEFAULT_VAR_DIR, sizeof(cfg_paths.var_dir));
|
||||
ast_copy_string(cfg_paths.data_dir, DEFAULT_DATA_DIR, sizeof(cfg_paths.data_dir));
|
||||
ast_copy_string(cfg_paths.log_dir, DEFAULT_LOG_DIR, sizeof(cfg_paths.log_dir));
|
||||
|
@ -3341,6 +3344,7 @@ static void ast_readconfig(void)
|
|||
} else if (!strcasecmp(v->name, "astspooldir")) {
|
||||
ast_copy_string(cfg_paths.spool_dir, v->value, sizeof(cfg_paths.spool_dir));
|
||||
snprintf(cfg_paths.monitor_dir, sizeof(cfg_paths.monitor_dir), "%s/monitor", v->value);
|
||||
snprintf(cfg_paths.recording_dir, sizeof(cfg_paths.recording_dir), "%s/recording", v->value);
|
||||
} else if (!strcasecmp(v->name, "astvarlibdir")) {
|
||||
ast_copy_string(cfg_paths.var_dir, v->value, sizeof(cfg_paths.var_dir));
|
||||
if (!found.dbdir)
|
||||
|
|
|
@ -3029,6 +3029,15 @@ int ast_answer(struct ast_channel *chan)
|
|||
return __ast_answer(chan, 0);
|
||||
}
|
||||
|
||||
inline int ast_auto_answer(struct ast_channel *chan)
|
||||
{
|
||||
if (ast_channel_state(chan) == AST_STATE_UP) {
|
||||
/* Already answered */
|
||||
return 0;
|
||||
}
|
||||
return ast_answer(chan);
|
||||
}
|
||||
|
||||
int ast_channel_get_duration(struct ast_channel *chan)
|
||||
{
|
||||
ast_assert(NULL != chan);
|
||||
|
|
|
@ -1020,6 +1020,9 @@ int ast_closestream(struct ast_filestream *f)
|
|||
* We close the stream in order to quit queuing frames now, because we might
|
||||
* change the writeformat, which could result in a subsequent write error, if
|
||||
* the format is different. */
|
||||
if (f == NULL) {
|
||||
return 0;
|
||||
}
|
||||
filestream_close(f);
|
||||
ao2_ref(f, -1);
|
||||
return 0;
|
||||
|
|
94
main/utils.c
94
main/utils.c
|
@ -2105,6 +2105,100 @@ int ast_mkdir(const char *path, int mode)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int safe_mkdir(const char *base_path, char *path, int mode)
|
||||
{
|
||||
RAII_VAR(char *, absolute_path, NULL, free);
|
||||
|
||||
absolute_path = realpath(path, NULL);
|
||||
|
||||
if (absolute_path) {
|
||||
/* Path exists, but is it in the right place? */
|
||||
if (!ast_begins_with(absolute_path, base_path)) {
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
/* It is in the right place! */
|
||||
return 0;
|
||||
} else {
|
||||
/* Path doesn't exist. */
|
||||
|
||||
/* The slash terminating the subpath we're checking */
|
||||
char *path_term = strchr(path, '/');
|
||||
/* True indicates the parent path is within base_path */
|
||||
int parent_is_safe = 0;
|
||||
int res;
|
||||
|
||||
while (path_term) {
|
||||
RAII_VAR(char *, absolute_subpath, NULL, free);
|
||||
|
||||
/* Truncate the path one past the slash */
|
||||
char c = *(path_term + 1);
|
||||
*(path_term + 1) = '\0';
|
||||
absolute_subpath = realpath(path, NULL);
|
||||
|
||||
if (absolute_subpath) {
|
||||
/* Subpath exists, but is it safe? */
|
||||
parent_is_safe = ast_begins_with(
|
||||
absolute_subpath, base_path);
|
||||
} else if (parent_is_safe) {
|
||||
/* Subpath does not exist, but parent is safe
|
||||
* Create it */
|
||||
res = mkdir(path, mode);
|
||||
if (res != 0) {
|
||||
ast_assert(errno != EEXIST);
|
||||
return errno;
|
||||
}
|
||||
} else {
|
||||
/* Subpath did not exist, parent was not safe
|
||||
* Fail! */
|
||||
errno = EPERM;
|
||||
return errno;
|
||||
}
|
||||
/* Restore the path */
|
||||
*(path_term + 1) = c;
|
||||
/* Move on to the next slash */
|
||||
path_term = strchr(path_term + 1, '/');
|
||||
}
|
||||
|
||||
/* Now to build the final path, but only if it's safe */
|
||||
if (!parent_is_safe) {
|
||||
errno = EPERM;
|
||||
return errno;
|
||||
}
|
||||
|
||||
res = mkdir(path, mode);
|
||||
if (res != 0 && errno != EEXIST) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ast_safe_mkdir(const char *base_path, const char *path, int mode)
|
||||
{
|
||||
RAII_VAR(char *, absolute_base_path, NULL, free);
|
||||
RAII_VAR(char *, p, NULL, ast_free);
|
||||
|
||||
if (base_path == NULL || path == NULL) {
|
||||
errno = EFAULT;
|
||||
return errno;
|
||||
}
|
||||
|
||||
p = ast_strdup(path);
|
||||
if (p == NULL) {
|
||||
errno = ENOMEM;
|
||||
return errno;
|
||||
}
|
||||
|
||||
absolute_base_path = realpath(base_path, NULL);
|
||||
if (absolute_base_path == NULL) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
return safe_mkdir(absolute_base_path, p, mode);
|
||||
}
|
||||
|
||||
int ast_utils_init(void)
|
||||
{
|
||||
dev_urandom_fd = open("/dev/urandom", O_RDONLY);
|
||||
|
|
|
@ -387,10 +387,10 @@ static void stasis_http_record_bridge_cb(
|
|||
args.max_silence_seconds = atoi(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "append") == 0) {
|
||||
args.append = atoi(i->value);
|
||||
args.append = ast_true(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "beep") == 0) {
|
||||
args.beep = atoi(i->value);
|
||||
args.beep = ast_true(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "terminateOn") == 0) {
|
||||
args.terminate_on = (i->value);
|
||||
|
|
|
@ -765,11 +765,11 @@ static void stasis_http_record_channel_cb(
|
|||
if (strcmp(i->name, "maxSilenceSeconds") == 0) {
|
||||
args.max_silence_seconds = atoi(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "append") == 0) {
|
||||
args.append = atoi(i->value);
|
||||
if (strcmp(i->name, "ifExists") == 0) {
|
||||
args.if_exists = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "beep") == 0) {
|
||||
args.beep = atoi(i->value);
|
||||
args.beep = ast_true(i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "terminateOn") == 0) {
|
||||
args.terminate_on = (i->value);
|
||||
|
@ -788,8 +788,9 @@ static void stasis_http_record_channel_cb(
|
|||
|
||||
switch (code) {
|
||||
case 500: /* Internal server error */
|
||||
case 400: /* Invalid parameters */
|
||||
case 404: /* Channel not found */
|
||||
case 409: /* Channel is not in a Stasis application, or the channel is currently bridged with other channels. */
|
||||
case 409: /* Channel is not in a Stasis application; the channel is currently bridged with other channels; A recording with the same name is currently in progress. */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -91,7 +91,7 @@ static void stasis_http_get_stored_recordings_cb(
|
|||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/stored/{recordingId}.
|
||||
* \brief Parameter parsing callback for /recordings/stored/{recordingName}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -110,8 +110,8 @@ static void stasis_http_get_stored_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -128,20 +128,20 @@ static void stasis_http_get_stored_recording_cb(
|
|||
is_valid = ari_validate_stored_recording(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingId}\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingName}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingId}\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingName}\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/stored/{recordingId}.
|
||||
* \brief Parameter parsing callback for /recordings/stored/{recordingName}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -160,8 +160,8 @@ static void stasis_http_delete_stored_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -178,13 +178,13 @@ static void stasis_http_delete_stored_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingId}\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingName}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingId}\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingName}\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ static void stasis_http_get_live_recordings_cb(
|
|||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -252,8 +252,8 @@ static void stasis_http_get_live_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -270,20 +270,20 @@ static void stasis_http_get_live_recording_cb(
|
|||
is_valid = ari_validate_live_recording(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -302,8 +302,8 @@ static void stasis_http_cancel_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -320,20 +320,20 @@ static void stasis_http_cancel_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}/stop.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}/stop.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -352,8 +352,8 @@ static void stasis_http_stop_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -370,20 +370,20 @@ static void stasis_http_stop_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/stop\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/stop\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/stop\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/stop\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}/pause.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}/pause.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -402,8 +402,8 @@ static void stasis_http_pause_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -420,20 +420,20 @@ static void stasis_http_pause_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/pause\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/pause\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/pause\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/pause\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}/unpause.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}/unpause.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -452,8 +452,8 @@ static void stasis_http_unpause_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -470,20 +470,20 @@ static void stasis_http_unpause_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/unpause\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/unpause\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/unpause\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/unpause\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}/mute.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}/mute.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -502,8 +502,8 @@ static void stasis_http_mute_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -520,20 +520,20 @@ static void stasis_http_mute_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/mute\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/mute\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/mute\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/mute\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingId}/unmute.
|
||||
* \brief Parameter parsing callback for /recordings/live/{recordingName}/unmute.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
|
@ -552,8 +552,8 @@ static void stasis_http_unmute_recording_cb(
|
|||
struct ast_variable *i;
|
||||
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "recordingId") == 0) {
|
||||
args.recording_id = (i->value);
|
||||
if (strcmp(i->name, "recordingName") == 0) {
|
||||
args.recording_name = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
|
@ -570,13 +570,13 @@ static void stasis_http_unmute_recording_cb(
|
|||
is_valid = ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/unmute\n", code);
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/unmute\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/unmute\n");
|
||||
ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/unmute\n");
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
|
@ -584,8 +584,8 @@ static void stasis_http_unmute_recording_cb(
|
|||
}
|
||||
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_stored_recordingId = {
|
||||
.path_segment = "recordingId",
|
||||
static struct stasis_rest_handlers recordings_stored_recordingName = {
|
||||
.path_segment = "recordingName",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = stasis_http_get_stored_recording_cb,
|
||||
|
@ -601,10 +601,10 @@ static struct stasis_rest_handlers recordings_stored = {
|
|||
[AST_HTTP_GET] = stasis_http_get_stored_recordings_cb,
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &recordings_stored_recordingId, }
|
||||
.children = { &recordings_stored_recordingName, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live_recordingId_stop = {
|
||||
static struct stasis_rest_handlers recordings_live_recordingName_stop = {
|
||||
.path_segment = "stop",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = stasis_http_stop_recording_cb,
|
||||
|
@ -613,7 +613,7 @@ static struct stasis_rest_handlers recordings_live_recordingId_stop = {
|
|||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live_recordingId_pause = {
|
||||
static struct stasis_rest_handlers recordings_live_recordingName_pause = {
|
||||
.path_segment = "pause",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = stasis_http_pause_recording_cb,
|
||||
|
@ -622,7 +622,7 @@ static struct stasis_rest_handlers recordings_live_recordingId_pause = {
|
|||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live_recordingId_unpause = {
|
||||
static struct stasis_rest_handlers recordings_live_recordingName_unpause = {
|
||||
.path_segment = "unpause",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = stasis_http_unpause_recording_cb,
|
||||
|
@ -631,7 +631,7 @@ static struct stasis_rest_handlers recordings_live_recordingId_unpause = {
|
|||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live_recordingId_mute = {
|
||||
static struct stasis_rest_handlers recordings_live_recordingName_mute = {
|
||||
.path_segment = "mute",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = stasis_http_mute_recording_cb,
|
||||
|
@ -640,7 +640,7 @@ static struct stasis_rest_handlers recordings_live_recordingId_mute = {
|
|||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live_recordingId_unmute = {
|
||||
static struct stasis_rest_handlers recordings_live_recordingName_unmute = {
|
||||
.path_segment = "unmute",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = stasis_http_unmute_recording_cb,
|
||||
|
@ -649,15 +649,15 @@ static struct stasis_rest_handlers recordings_live_recordingId_unmute = {
|
|||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live_recordingId = {
|
||||
.path_segment = "recordingId",
|
||||
static struct stasis_rest_handlers recordings_live_recordingName = {
|
||||
.path_segment = "recordingName",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = stasis_http_get_live_recording_cb,
|
||||
[AST_HTTP_DELETE] = stasis_http_cancel_recording_cb,
|
||||
},
|
||||
.num_children = 5,
|
||||
.children = { &recordings_live_recordingId_stop,&recordings_live_recordingId_pause,&recordings_live_recordingId_unpause,&recordings_live_recordingId_mute,&recordings_live_recordingId_unmute, }
|
||||
.children = { &recordings_live_recordingName_stop,&recordings_live_recordingName_pause,&recordings_live_recordingName_unpause,&recordings_live_recordingName_mute,&recordings_live_recordingName_unmute, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings_live = {
|
||||
|
@ -666,7 +666,7 @@ static struct stasis_rest_handlers recordings_live = {
|
|||
[AST_HTTP_GET] = stasis_http_get_live_recordings_cb,
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &recordings_live_recordingId, }
|
||||
.children = { &recordings_live_recordingName, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/recordings.{format} */
|
||||
static struct stasis_rest_handlers recordings = {
|
||||
|
|
|
@ -37,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
#include "asterisk/file.h"
|
||||
#include "asterisk/logger.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/paths.h"
|
||||
#include "asterisk/stasis_app_impl.h"
|
||||
#include "asterisk/stasis_app_playback.h"
|
||||
#include "asterisk/stasis_channels.h"
|
||||
|
@ -195,7 +196,7 @@ static void *play_uri(struct stasis_app_control *control,
|
|||
RAII_VAR(struct stasis_app_playback *, playback, NULL,
|
||||
playback_cleanup);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
const char *file;
|
||||
RAII_VAR(char *, file, NULL, ast_free);
|
||||
int res;
|
||||
long offsetms;
|
||||
|
||||
|
@ -225,16 +226,27 @@ static void *play_uri(struct stasis_app_control *control,
|
|||
|
||||
if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
|
||||
/* Play sound */
|
||||
file = playback->media + strlen(SOUND_URI_SCHEME);
|
||||
file = ast_strdup(playback->media + strlen(SOUND_URI_SCHEME));
|
||||
} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
|
||||
/* Play recording */
|
||||
file = playback->media + strlen(RECORDING_URI_SCHEME);
|
||||
const char *relname =
|
||||
playback->media + strlen(RECORDING_URI_SCHEME);
|
||||
if (relname[0] == '/') {
|
||||
file = ast_strdup(relname);
|
||||
} else {
|
||||
ast_asprintf(&file, "%s/%s",
|
||||
ast_config_AST_RECORDING_DIR, relname);
|
||||
}
|
||||
} else {
|
||||
/* Play URL */
|
||||
ast_log(LOG_ERROR, "Unimplemented\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = ast_control_streamfile_lang(chan, file, fwd, rev, stop, pause,
|
||||
restart, playback->skipms, playback->language, &offsetms);
|
||||
|
||||
|
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* David M. Lee, II <dlee@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 res_stasis recording support.
|
||||
*
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend type="module">res_stasis</depend>
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/dsp.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/paths.h"
|
||||
#include "asterisk/stasis_app_impl.h"
|
||||
#include "asterisk/stasis_app_recording.h"
|
||||
#include "asterisk/stasis_channels.h"
|
||||
|
||||
/*! Number of hash buckets for recording container. Keep it prime! */
|
||||
#define RECORDING_BUCKETS 127
|
||||
|
||||
/*! Comment is ignored by most formats, so we will ignore it, too. */
|
||||
#define RECORDING_COMMENT NULL
|
||||
|
||||
/*! Recording check is unimplemented. le sigh */
|
||||
#define RECORDING_CHECK 0
|
||||
|
||||
STASIS_MESSAGE_TYPE_DEFN(stasis_app_recording_snapshot_type);
|
||||
|
||||
/*! Container of all current recordings */
|
||||
static struct ao2_container *recordings;
|
||||
|
||||
struct stasis_app_recording {
|
||||
/*! Recording options. */
|
||||
struct stasis_app_recording_options *options;
|
||||
/*! Absolute path (minus extension) of the recording */
|
||||
char *absolute_name;
|
||||
/*! Control object for the channel we're playing back to */
|
||||
struct stasis_app_control *control;
|
||||
|
||||
/*! Current state of the recording. */
|
||||
enum stasis_app_recording_state state;
|
||||
};
|
||||
|
||||
static int recording_hash(const void *obj, int flags)
|
||||
{
|
||||
const struct stasis_app_recording *recording = obj;
|
||||
const char *id = flags & OBJ_KEY ? obj : recording->options->name;
|
||||
return ast_str_hash(id);
|
||||
}
|
||||
|
||||
static int recording_cmp(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct stasis_app_recording *lhs = obj;
|
||||
struct stasis_app_recording *rhs = arg;
|
||||
const char *rhs_id = flags & OBJ_KEY ? arg : rhs->options->name;
|
||||
|
||||
if (strcmp(lhs->options->name, rhs_id) == 0) {
|
||||
return CMP_MATCH | CMP_STOP;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *state_to_string(enum stasis_app_recording_state state)
|
||||
{
|
||||
switch (state) {
|
||||
case STASIS_APP_RECORDING_STATE_QUEUED:
|
||||
return "queued";
|
||||
case STASIS_APP_RECORDING_STATE_RECORDING:
|
||||
return "recording";
|
||||
case STASIS_APP_RECORDING_STATE_PAUSED:
|
||||
return "paused";
|
||||
case STASIS_APP_RECORDING_STATE_COMPLETE:
|
||||
return "done";
|
||||
case STASIS_APP_RECORDING_STATE_FAILED:
|
||||
return "failed";
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void recording_options_dtor(void *obj)
|
||||
{
|
||||
struct stasis_app_recording_options *options = obj;
|
||||
|
||||
ast_string_field_free_memory(options);
|
||||
}
|
||||
|
||||
struct stasis_app_recording_options *stasis_app_recording_options_create(
|
||||
const char *name, const char *format)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_recording_options *, options, NULL,
|
||||
ao2_cleanup);
|
||||
|
||||
options = ao2_alloc(sizeof(*options), recording_options_dtor);
|
||||
|
||||
if (!options || ast_string_field_init(options, 128)) {
|
||||
return NULL;
|
||||
}
|
||||
ast_string_field_set(options, name, name);
|
||||
ast_string_field_set(options, format, format);
|
||||
|
||||
ao2_ref(options, +1);
|
||||
return options;
|
||||
}
|
||||
|
||||
char stasis_app_recording_termination_parse(const char *str)
|
||||
{
|
||||
if (ast_strlen_zero(str)) {
|
||||
return STASIS_APP_RECORDING_TERMINATE_NONE;
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "none") == 0) {
|
||||
return STASIS_APP_RECORDING_TERMINATE_NONE;
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "any") == 0) {
|
||||
return STASIS_APP_RECORDING_TERMINATE_ANY;
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "#") == 0) {
|
||||
return '#';
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "*") == 0) {
|
||||
return '*';
|
||||
}
|
||||
|
||||
return STASIS_APP_RECORDING_TERMINATE_INVALID;
|
||||
}
|
||||
|
||||
enum ast_record_if_exists stasis_app_recording_if_exists_parse(
|
||||
const char *str)
|
||||
{
|
||||
if (ast_strlen_zero(str)) {
|
||||
/* Default value */
|
||||
return AST_RECORD_IF_EXISTS_FAIL;
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "fail") == 0) {
|
||||
return AST_RECORD_IF_EXISTS_FAIL;
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "overwrite") == 0) {
|
||||
return AST_RECORD_IF_EXISTS_OVERWRITE;
|
||||
}
|
||||
|
||||
if (strcasecmp(str, "append") == 0) {
|
||||
return AST_RECORD_IF_EXISTS_APPEND;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void recording_publish(struct stasis_app_recording *recording)
|
||||
{
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
|
||||
|
||||
ast_assert(recording != NULL);
|
||||
|
||||
json = stasis_app_recording_to_json(recording);
|
||||
if (json == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
message = ast_channel_blob_create_from_cache(
|
||||
stasis_app_control_get_channel_id(recording->control),
|
||||
stasis_app_recording_snapshot_type(), json);
|
||||
if (message == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
stasis_app_control_publish(recording->control, message);
|
||||
}
|
||||
|
||||
static void recording_fail(struct stasis_app_recording *recording)
|
||||
{
|
||||
SCOPED_AO2LOCK(lock, recording);
|
||||
recording->state = STASIS_APP_RECORDING_STATE_FAILED;
|
||||
recording_publish(recording);
|
||||
}
|
||||
|
||||
static void recording_cleanup(struct stasis_app_recording *recording)
|
||||
{
|
||||
ao2_unlink_flags(recordings, recording,
|
||||
OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
|
||||
}
|
||||
|
||||
static void *record_file(struct stasis_app_control *control,
|
||||
struct ast_channel *chan, void *data)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_recording *, recording,
|
||||
NULL, recording_cleanup);
|
||||
char *acceptdtmf;
|
||||
int res;
|
||||
int duration = 0;
|
||||
|
||||
recording = data;
|
||||
ast_assert(recording != NULL);
|
||||
|
||||
ao2_lock(recording);
|
||||
recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
|
||||
recording_publish(recording);
|
||||
ao2_unlock(recording);
|
||||
|
||||
switch (recording->options->terminate_on) {
|
||||
case STASIS_APP_RECORDING_TERMINATE_NONE:
|
||||
case STASIS_APP_RECORDING_TERMINATE_INVALID:
|
||||
acceptdtmf = "";
|
||||
break;
|
||||
case STASIS_APP_RECORDING_TERMINATE_ANY:
|
||||
acceptdtmf = "#*0123456789abcd";
|
||||
break;
|
||||
default:
|
||||
acceptdtmf = ast_alloca(2);
|
||||
acceptdtmf[0] = recording->options->terminate_on;
|
||||
acceptdtmf[1] = '\0';
|
||||
}
|
||||
|
||||
res = ast_auto_answer(chan);
|
||||
if (res != 0) {
|
||||
ast_debug(3, "%s: Failed to answer\n",
|
||||
ast_channel_uniqueid(chan));
|
||||
recording_fail(recording);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_play_and_record_full(chan,
|
||||
recording->options->beep ? "beep" : NULL,
|
||||
recording->absolute_name,
|
||||
recording->options->max_duration_seconds,
|
||||
recording->options->format,
|
||||
&duration,
|
||||
NULL, /* sound_duration */
|
||||
-1, /* silencethreshold */
|
||||
recording->options->max_silence_seconds * 1000,
|
||||
NULL, /* path */
|
||||
acceptdtmf,
|
||||
NULL, /* canceldtmf */
|
||||
1, /* skip_confirmation_sound */
|
||||
recording->options->if_exists);
|
||||
|
||||
ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
|
||||
|
||||
ao2_lock(recording);
|
||||
recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
|
||||
recording_publish(recording);
|
||||
ao2_unlock(recording);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void recording_dtor(void *obj)
|
||||
{
|
||||
struct stasis_app_recording *recording = obj;
|
||||
|
||||
ao2_cleanup(recording->options);
|
||||
}
|
||||
|
||||
struct stasis_app_recording *stasis_app_control_record(
|
||||
struct stasis_app_control *control,
|
||||
struct stasis_app_recording_options *options)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
|
||||
char *last_slash;
|
||||
|
||||
errno = 0;
|
||||
|
||||
if (options == NULL ||
|
||||
ast_strlen_zero(options->name) ||
|
||||
ast_strlen_zero(options->format) ||
|
||||
options->max_silence_seconds < 0 ||
|
||||
options->max_duration_seconds < 0) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_debug(3, "%s: Sending record(%s.%s) command\n",
|
||||
stasis_app_control_get_channel_id(control), options->name,
|
||||
options->format);
|
||||
|
||||
recording = ao2_alloc(sizeof(*recording), recording_dtor);
|
||||
if (!recording) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_asprintf(&recording->absolute_name, "%s/%s",
|
||||
ast_config_AST_RECORDING_DIR, options->name);
|
||||
|
||||
if (recording->absolute_name == NULL) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((last_slash = strrchr(recording->absolute_name, '/'))) {
|
||||
*last_slash = '\0';
|
||||
if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
|
||||
recording->absolute_name, 0777) != 0) {
|
||||
/* errno set by ast_mkdir */
|
||||
return NULL;
|
||||
}
|
||||
*last_slash = '/';
|
||||
}
|
||||
|
||||
ao2_ref(options, +1);
|
||||
recording->options = options;
|
||||
recording->control = control;
|
||||
recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
|
||||
|
||||
{
|
||||
RAII_VAR(struct stasis_app_recording *, old_recording, NULL,
|
||||
ao2_cleanup);
|
||||
|
||||
SCOPED_AO2LOCK(lock, recordings);
|
||||
|
||||
old_recording = ao2_find(recordings, options->name,
|
||||
OBJ_KEY | OBJ_NOLOCK);
|
||||
if (old_recording) {
|
||||
ast_log(LOG_WARNING,
|
||||
"Recording %s already in progress\n",
|
||||
recording->options->name);
|
||||
errno = EEXIST;
|
||||
return NULL;
|
||||
}
|
||||
ao2_link(recordings, recording);
|
||||
}
|
||||
|
||||
/* A ref is kept in the recordings container; no need to bump */
|
||||
stasis_app_send_command_async(control, record_file, recording);
|
||||
|
||||
/* Although this should be bumped for the caller */
|
||||
ao2_ref(recording, +1);
|
||||
return recording;
|
||||
}
|
||||
|
||||
enum stasis_app_recording_state stasis_app_recording_get_state(
|
||||
struct stasis_app_recording *recording)
|
||||
{
|
||||
return recording->state;
|
||||
}
|
||||
|
||||
const char *stasis_app_recording_get_name(
|
||||
struct stasis_app_recording *recording)
|
||||
{
|
||||
return recording->options->name;
|
||||
}
|
||||
|
||||
struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
|
||||
|
||||
recording = ao2_find(recordings, name, OBJ_KEY);
|
||||
if (recording == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ao2_ref(recording, +1);
|
||||
return recording;
|
||||
}
|
||||
|
||||
struct ast_json *stasis_app_recording_to_json(
|
||||
const struct stasis_app_recording *recording)
|
||||
{
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
|
||||
if (recording == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json = ast_json_pack("{s: s, s: s, s: s}",
|
||||
"name", recording->options->name,
|
||||
"format", recording->options->format,
|
||||
"state", state_to_string(recording->state));
|
||||
|
||||
return ast_json_ref(json);
|
||||
}
|
||||
|
||||
enum stasis_app_recording_oper_results stasis_app_recording_operation(
|
||||
struct stasis_app_recording *recording,
|
||||
enum stasis_app_recording_media_operation operation)
|
||||
{
|
||||
ast_assert(0); // TODO
|
||||
return STASIS_APP_RECORDING_OPER_FAILED;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type);
|
||||
if (r != 0) {
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
|
||||
recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash,
|
||||
recording_cmp);
|
||||
if (!recordings) {
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
ao2_cleanup(recordings);
|
||||
recordings = NULL;
|
||||
STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS,
|
||||
"Stasis application recording support",
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.nonoptreq = "res_stasis");
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
global:
|
||||
LINKER_SYMBOL_PREFIXstasis_app_*;
|
||||
local:
|
||||
*;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
/* -*- C -*-
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2012 - 2013, Digium, Inc.
|
||||
|
@ -39,6 +39,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
#include "asterisk/callerid.h"
|
||||
#include "asterisk/stasis_app.h"
|
||||
#include "asterisk/stasis_app_playback.h"
|
||||
#include "asterisk/stasis_app_recording.h"
|
||||
#include "asterisk/stasis_channels.h"
|
||||
#include "resource_channels.h"
|
||||
|
||||
|
@ -249,10 +250,139 @@ void stasis_http_play_on_channel(struct ast_variable *headers,
|
|||
|
||||
stasis_http_response_created(response, playback_url, json);
|
||||
}
|
||||
void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response)
|
||||
|
||||
void stasis_http_record_channel(struct ast_variable *headers,
|
||||
struct ast_record_channel_args *args,
|
||||
struct stasis_http_response *response)
|
||||
{
|
||||
ast_log(LOG_ERROR, "TODO: stasis_http_record_channel\n");
|
||||
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
|
||||
RAII_VAR(char *, recording_url, NULL, ast_free);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
RAII_VAR(struct stasis_app_recording_options *, options, NULL,
|
||||
ao2_cleanup);
|
||||
RAII_VAR(char *, uri_encoded_name, NULL, ast_free);
|
||||
size_t uri_name_maxlen;
|
||||
|
||||
ast_assert(response != NULL);
|
||||
|
||||
if (args->max_duration_seconds < 0) {
|
||||
stasis_http_response_error(
|
||||
response, 400, "Bad Request",
|
||||
"max_duration_seconds cannot be negative");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args->max_silence_seconds < 0) {
|
||||
stasis_http_response_error(
|
||||
response, 400, "Bad Request",
|
||||
"max_silence_seconds cannot be negative");
|
||||
return;
|
||||
}
|
||||
|
||||
control = find_control(response, args->channel_id);
|
||||
if (control == NULL) {
|
||||
/* Response filled in by find_control */
|
||||
return;
|
||||
}
|
||||
|
||||
options = stasis_app_recording_options_create(args->name, args->format);
|
||||
if (options == NULL) {
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
}
|
||||
options->max_silence_seconds = args->max_silence_seconds;
|
||||
options->max_duration_seconds = args->max_duration_seconds;
|
||||
options->terminate_on =
|
||||
stasis_app_recording_termination_parse(args->terminate_on);
|
||||
options->if_exists =
|
||||
stasis_app_recording_if_exists_parse(args->if_exists);
|
||||
options->beep = args->beep;
|
||||
|
||||
if (options->terminate_on == STASIS_APP_RECORDING_TERMINATE_INVALID) {
|
||||
stasis_http_response_error(
|
||||
response, 400, "Bad Request",
|
||||
"terminateOn invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (options->if_exists == -1) {
|
||||
stasis_http_response_error(
|
||||
response, 400, "Bad Request",
|
||||
"ifExists invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
recording = stasis_app_control_record(control, options);
|
||||
if (recording == NULL) {
|
||||
switch(errno) {
|
||||
case EINVAL:
|
||||
/* While the arguments are invalid, we should have
|
||||
* caught them prior to calling record.
|
||||
*/
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Error parsing request");
|
||||
break;
|
||||
case EEXIST:
|
||||
stasis_http_response_error(response, 409, "Conflict",
|
||||
"Recording '%s' already in progress",
|
||||
args->name);
|
||||
break;
|
||||
case ENOMEM:
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
break;
|
||||
case EPERM:
|
||||
stasis_http_response_error(
|
||||
response, 400, "Bad Request",
|
||||
"Recording name invalid");
|
||||
break;
|
||||
default:
|
||||
ast_log(LOG_WARNING,
|
||||
"Unrecognized recording error: %s\n",
|
||||
strerror(errno));
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Internal Server Error");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uri_name_maxlen = strlen(args->name) * 3;
|
||||
uri_encoded_name = ast_malloc(uri_name_maxlen);
|
||||
if (!uri_encoded_name) {
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
return;
|
||||
}
|
||||
ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen,
|
||||
ast_uri_http);
|
||||
|
||||
ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name);
|
||||
if (!recording_url) {
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
json = stasis_app_recording_to_json(recording);
|
||||
if (!json) {
|
||||
stasis_http_response_error(
|
||||
response, 500, "Internal Server Error",
|
||||
"Out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
stasis_http_response_created(response, recording_url, json);
|
||||
}
|
||||
|
||||
void stasis_http_get_channel(struct ast_variable *headers,
|
||||
struct ast_get_channel_args *args,
|
||||
struct stasis_http_response *response)
|
||||
|
|
|
@ -247,8 +247,8 @@ struct ast_record_channel_args {
|
|||
int max_duration_seconds;
|
||||
/*! \brief Maximum duration of silence, in seconds. 0 for no limit */
|
||||
int max_silence_seconds;
|
||||
/*! \brief If true, and recording already exists, append to recording */
|
||||
int append;
|
||||
/*! \brief Action to take if a recording with the same name already exists. */
|
||||
const char *if_exists;
|
||||
/*! \brief Play beep when recording begins */
|
||||
int beep;
|
||||
/*! \brief DTMF input to terminate recording */
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/stasis_app_recording.h"
|
||||
#include "resource_recordings.h"
|
||||
|
||||
void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response)
|
||||
|
@ -45,10 +46,31 @@ void stasis_http_get_live_recordings(struct ast_variable *headers, struct ast_ge
|
|||
{
|
||||
ast_log(LOG_ERROR, "TODO: stasis_http_get_live_recordings\n");
|
||||
}
|
||||
void stasis_http_get_live_recording(struct ast_variable *headers, struct ast_get_live_recording_args *args, struct stasis_http_response *response)
|
||||
|
||||
void stasis_http_get_live_recording(struct ast_variable *headers,
|
||||
struct ast_get_live_recording_args *args,
|
||||
struct stasis_http_response *response)
|
||||
{
|
||||
ast_log(LOG_ERROR, "TODO: stasis_http_get_live_recording\n");
|
||||
RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||
|
||||
recording = stasis_app_recording_find_by_name(args->recording_name);
|
||||
if (recording == NULL) {
|
||||
stasis_http_response_error(response, 404, "Not Found",
|
||||
"Recording not found");
|
||||
return;
|
||||
}
|
||||
|
||||
json = stasis_app_recording_to_json(recording);
|
||||
if (json == NULL) {
|
||||
stasis_http_response_error(response, 500,
|
||||
"Internal Server Error", "Error building response");
|
||||
return;
|
||||
}
|
||||
|
||||
stasis_http_response_ok(response, ast_json_ref(json));
|
||||
}
|
||||
|
||||
void stasis_http_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct stasis_http_response *response)
|
||||
{
|
||||
ast_log(LOG_ERROR, "TODO: stasis_http_cancel_recording\n");
|
||||
|
|
|
@ -52,8 +52,8 @@ struct ast_get_stored_recordings_args {
|
|||
void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_get_stored_recording() */
|
||||
struct ast_get_stored_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Get a stored recording's details.
|
||||
|
@ -65,8 +65,8 @@ struct ast_get_stored_recording_args {
|
|||
void stasis_http_get_stored_recording(struct ast_variable *headers, struct ast_get_stored_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_delete_stored_recording() */
|
||||
struct ast_delete_stored_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Delete a stored recording.
|
||||
|
@ -89,8 +89,8 @@ struct ast_get_live_recordings_args {
|
|||
void stasis_http_get_live_recordings(struct ast_variable *headers, struct ast_get_live_recordings_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_get_live_recording() */
|
||||
struct ast_get_live_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief List live recordings.
|
||||
|
@ -102,8 +102,8 @@ struct ast_get_live_recording_args {
|
|||
void stasis_http_get_live_recording(struct ast_variable *headers, struct ast_get_live_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_cancel_recording() */
|
||||
struct ast_cancel_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Stop a live recording and discard it.
|
||||
|
@ -115,8 +115,8 @@ struct ast_cancel_recording_args {
|
|||
void stasis_http_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_stop_recording() */
|
||||
struct ast_stop_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Stop a live recording and store it.
|
||||
|
@ -128,12 +128,14 @@ struct ast_stop_recording_args {
|
|||
void stasis_http_stop_recording(struct ast_variable *headers, struct ast_stop_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_pause_recording() */
|
||||
struct ast_pause_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Pause a live recording.
|
||||
*
|
||||
* Pausing a recording suspends silence detection, which will be restarted when the recording is unpaused.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
|
@ -141,8 +143,8 @@ struct ast_pause_recording_args {
|
|||
void stasis_http_pause_recording(struct ast_variable *headers, struct ast_pause_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_unpause_recording() */
|
||||
struct ast_unpause_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Unpause a live recording.
|
||||
|
@ -154,12 +156,14 @@ struct ast_unpause_recording_args {
|
|||
void stasis_http_unpause_recording(struct ast_variable *headers, struct ast_unpause_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_mute_recording() */
|
||||
struct ast_mute_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Mute a live recording.
|
||||
*
|
||||
* Muting a recording suspends silence detection, which will be restarted when the recording is unmuted.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
|
@ -167,8 +171,8 @@ struct ast_mute_recording_args {
|
|||
void stasis_http_mute_recording(struct ast_variable *headers, struct ast_mute_recording_args *args, struct stasis_http_response *response);
|
||||
/*! \brief Argument struct for stasis_http_unmute_recording() */
|
||||
struct ast_unmute_recording_args {
|
||||
/*! \brief Recording's id */
|
||||
const char *recording_id;
|
||||
/*! \brief The name of the recording */
|
||||
const char *recording_name;
|
||||
};
|
||||
/*!
|
||||
* \brief Unmute a live recording.
|
||||
|
|
|
@ -139,10 +139,11 @@ class AsteriskProcessor(SwaggerPostProcessor):
|
|||
|
||||
#: String conversion functions for string to C type.
|
||||
convert_mapping = {
|
||||
'const char *': '',
|
||||
'string': '',
|
||||
'int': 'atoi',
|
||||
'long': 'atol',
|
||||
'double': 'atof',
|
||||
'boolean': 'ast_true',
|
||||
}
|
||||
|
||||
def __init__(self, wiki_prefix):
|
||||
|
@ -194,7 +195,7 @@ class AsteriskProcessor(SwaggerPostProcessor):
|
|||
# Parameter names are camelcase, Asterisk convention is snake case
|
||||
parameter.c_name = snakify(parameter.name)
|
||||
parameter.c_data_type = self.type_mapping[parameter.data_type]
|
||||
parameter.c_convert = self.convert_mapping[parameter.c_data_type]
|
||||
parameter.c_convert = self.convert_mapping[parameter.data_type]
|
||||
# You shouldn't put a space between 'char *' and the variable
|
||||
if parameter.c_data_type.endswith('*'):
|
||||
parameter.c_space = ''
|
||||
|
|
|
@ -246,11 +246,9 @@ def load_allowable_values(json, context):
|
|||
value_type = json['valueType']
|
||||
|
||||
if value_type == 'RANGE':
|
||||
if not 'min' in json:
|
||||
raise SwaggerError("Missing field min", context)
|
||||
if not 'max' in json:
|
||||
raise SwaggerError("Missing field max", context)
|
||||
return AllowableRange(json['min'], json['max'])
|
||||
if not 'min' in json and not 'max' in json:
|
||||
raise SwaggerError("Missing fields min/max", context)
|
||||
return AllowableRange(json.get('min'), json.get('max'))
|
||||
if value_type == 'LIST':
|
||||
if not 'values' in json:
|
||||
raise SwaggerError("Missing field values", context)
|
||||
|
|
|
@ -565,7 +565,11 @@
|
|||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "int",
|
||||
"defaultValue": 0
|
||||
"defaultValue": 0,
|
||||
"allowableValues": {
|
||||
"valueType": "RANGE",
|
||||
"min": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "maxSilenceSeconds",
|
||||
|
@ -574,16 +578,28 @@
|
|||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "int",
|
||||
"defaultValue": 0
|
||||
"defaultValue": 0,
|
||||
"allowableValues": {
|
||||
"valueType": "RANGE",
|
||||
"min": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "append",
|
||||
"description": "If true, and recording already exists, append to recording",
|
||||
"name": "ifExists",
|
||||
"description": "Action to take if a recording with the same name already exists.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "boolean",
|
||||
"defaultValue": false
|
||||
"dataType": "string",
|
||||
"defaultValue": "fail",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"fail",
|
||||
"overwrite",
|
||||
"append"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beep",
|
||||
|
@ -614,13 +630,17 @@
|
|||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": 400,
|
||||
"reason": "Invalid parameters"
|
||||
},
|
||||
{
|
||||
"code": 404,
|
||||
"reason": "Channel not found"
|
||||
},
|
||||
{
|
||||
"code": 409,
|
||||
"reason": "Channel is not in a Stasis application, or the channel is currently bridged with other channels."
|
||||
"reason": "Channel is not in a Stasis application; the channel is currently bridged with other channels; A recording with the same name is currently in progress."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/stored/{recordingId}",
|
||||
"path": "/recordings/stored/{recordingName}",
|
||||
"description": "Individual recording",
|
||||
"operations": [
|
||||
{
|
||||
|
@ -30,8 +30,8 @@
|
|||
"responseClass": "StoredRecording",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -46,8 +46,8 @@
|
|||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -70,7 +70,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/live/{recordingId}",
|
||||
"path": "/recordings/live/{recordingName}",
|
||||
"description": "A recording that is in progress",
|
||||
"operations": [
|
||||
{
|
||||
|
@ -80,8 +80,8 @@
|
|||
"responseClass": "LiveRecording",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -96,8 +96,8 @@
|
|||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -108,7 +108,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/live/{recordingId}/stop",
|
||||
"path": "/recordings/live/{recordingName}/stop",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
|
@ -117,8 +117,8 @@
|
|||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -129,17 +129,18 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/live/{recordingId}/pause",
|
||||
"path": "/recordings/live/{recordingName}/pause",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
"summary": "Pause a live recording.",
|
||||
"notes": "Pausing a recording suspends silence detection, which will be restarted when the recording is unpaused.",
|
||||
"nickname": "pauseRecording",
|
||||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -150,7 +151,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/live/{recordingId}/unpause",
|
||||
"path": "/recordings/live/{recordingName}/unpause",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
|
@ -159,8 +160,8 @@
|
|||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -171,17 +172,18 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/live/{recordingId}/mute",
|
||||
"path": "/recordings/live/{recordingName}/mute",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
"summary": "Mute a live recording.",
|
||||
"notes": "Muting a recording suspends silence detection, which will be restarted when the recording is unmuted.",
|
||||
"nickname": "muteRecording",
|
||||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
@ -192,7 +194,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"path": "/recordings/live/{recordingId}/unmute",
|
||||
"path": "/recordings/live/{recordingName}/unmute",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
|
@ -201,8 +203,8 @@
|
|||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "recordingId",
|
||||
"description": "Recording's id",
|
||||
"name": "recordingName",
|
||||
"description": "The name of the recording",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
|
|
|
@ -42,6 +42,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
|
|||
#include "asterisk/channel.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
AST_TEST_DEFINE(uri_encode_decode_test)
|
||||
{
|
||||
int res = AST_TEST_PASS;
|
||||
|
@ -421,6 +423,93 @@ AST_TEST_DEFINE(agi_loaded_test)
|
|||
return res;
|
||||
}
|
||||
|
||||
AST_TEST_DEFINE(safe_mkdir_test)
|
||||
{
|
||||
char base_path[] = "/tmp/safe_mkdir.XXXXXX";
|
||||
char path[80] = {};
|
||||
int res;
|
||||
struct stat actual;
|
||||
|
||||
switch (cmd) {
|
||||
case TEST_INIT:
|
||||
info->name = __func__;
|
||||
info->category = "/main/utils/";
|
||||
info->summary = "Safe mkdir test";
|
||||
info->description =
|
||||
"This test ensures that ast_safe_mkdir does what it is "
|
||||
"supposed to";
|
||||
return AST_TEST_NOT_RUN;
|
||||
case TEST_EXECUTE:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mkdtemp(base_path) == NULL) {
|
||||
ast_test_status_update(test, "Failed to create tmpdir for test\n");
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "%s/should_work", base_path);
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 == res);
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 == res);
|
||||
ast_test_validate(test, S_ISDIR(actual.st_mode));
|
||||
|
||||
snprintf(path, sizeof(path), "%s/should/also/work", base_path);
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 == res);
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 == res);
|
||||
ast_test_validate(test, S_ISDIR(actual.st_mode));
|
||||
|
||||
snprintf(path, sizeof(path), "%s/even/this/../should/work", base_path);
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 == res);
|
||||
snprintf(path, sizeof(path), "%s/even/should/work", base_path);
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 == res);
|
||||
ast_test_validate(test, S_ISDIR(actual.st_mode));
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
"%s/surprisingly/this/should//////////////////work", base_path);
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 == res);
|
||||
snprintf(path, sizeof(path),
|
||||
"%s/surprisingly/this/should/work", base_path);
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 == res);
|
||||
ast_test_validate(test, S_ISDIR(actual.st_mode));
|
||||
|
||||
snprintf(path, sizeof(path), "/should_not_work");
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 != res);
|
||||
ast_test_validate(test, EPERM == errno);
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 != res);
|
||||
ast_test_validate(test, ENOENT == errno);
|
||||
|
||||
snprintf(path, sizeof(path), "%s/../nor_should_this", base_path);
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 != res);
|
||||
ast_test_validate(test, EPERM == errno);
|
||||
strncpy(path, "/tmp/nor_should_this", sizeof(path));
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 != res);
|
||||
ast_test_validate(test, ENOENT == errno);
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
"%s/this/especially/should/not/../../../../../work", base_path);
|
||||
res = ast_safe_mkdir(base_path, path, 0777);
|
||||
ast_test_validate(test, 0 != res);
|
||||
ast_test_validate(test, EPERM == errno);
|
||||
strncpy(path, "/tmp/work", sizeof(path));
|
||||
res = stat(path, &actual);
|
||||
ast_test_validate(test, 0 != res);
|
||||
ast_test_validate(test, ENOENT == errno);
|
||||
|
||||
return AST_TEST_PASS;
|
||||
}
|
||||
|
||||
AST_TEST_DEFINE(crypt_test)
|
||||
{
|
||||
RAII_VAR(char *, password_crypted, NULL, ast_free);
|
||||
|
@ -467,6 +556,7 @@ static int unload_module(void)
|
|||
AST_TEST_UNREGISTER(crypto_loaded_test);
|
||||
AST_TEST_UNREGISTER(adsi_loaded_test);
|
||||
AST_TEST_UNREGISTER(agi_loaded_test);
|
||||
AST_TEST_UNREGISTER(safe_mkdir_test);
|
||||
AST_TEST_UNREGISTER(crypt_test);
|
||||
return 0;
|
||||
}
|
||||
|
@ -481,6 +571,7 @@ static int load_module(void)
|
|||
AST_TEST_REGISTER(crypto_loaded_test);
|
||||
AST_TEST_REGISTER(adsi_loaded_test);
|
||||
AST_TEST_REGISTER(agi_loaded_test);
|
||||
AST_TEST_REGISTER(safe_mkdir_test);
|
||||
AST_TEST_REGISTER(crypt_test);
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue