| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! 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; | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 	/*! Control object for the channel we're recording */ | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	struct stasis_app_control *control; | 
					
						
							|  |  |  | 	/*! Current state of the recording. */ | 
					
						
							|  |  |  | 	enum stasis_app_recording_state state; | 
					
						
							| 
									
										
										
										
											2014-07-25 14:47:09 +00:00
										 |  |  | 	/*! Duration calculations */ | 
					
						
							|  |  |  | 	struct { | 
					
						
							|  |  |  | 		/*! Total duration */ | 
					
						
							|  |  |  | 		int total; | 
					
						
							|  |  |  | 		/*! Duration minus any silence */ | 
					
						
							|  |  |  | 		int energy_only; | 
					
						
							|  |  |  | 	} duration; | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 	/*! Indicates whether the recording is currently muted */ | 
					
						
							|  |  |  | 	int muted:1; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-23 12:52:54 +00:00
										 |  |  | static struct ast_json *recording_to_json(struct stasis_message *message, | 
					
						
							|  |  |  | 	const struct stasis_message_sanitizer *sanitize) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_channel_blob *channel_blob = stasis_message_data(message); | 
					
						
							|  |  |  | 	struct ast_json *blob = channel_blob->blob; | 
					
						
							|  |  |  | 	const char *state = | 
					
						
							|  |  |  | 		ast_json_string_get(ast_json_object_get(blob, "state")); | 
					
						
							|  |  |  | 	const char *type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!strcmp(state, "recording")) { | 
					
						
							|  |  |  | 		type = "RecordingStarted"; | 
					
						
							|  |  |  | 	} else if (!strcmp(state, "done") || !strcasecmp(state, "canceled")) { | 
					
						
							|  |  |  | 		type = "RecordingFinished"; | 
					
						
							|  |  |  | 	} else if (!strcmp(state, "failed")) { | 
					
						
							|  |  |  | 		type = "RecordingFailed"; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-14 14:04:15 -04:00
										 |  |  | 	return ast_json_pack("{s: s, s: o}", | 
					
						
							| 
									
										
										
										
											2013-11-23 12:52:54 +00:00
										 |  |  | 		"type", type, | 
					
						
							| 
									
										
										
										
											2015-12-14 14:04:15 -04:00
										 |  |  | 		"recording", ast_json_deep_copy(blob)); | 
					
						
							| 
									
										
										
										
											2013-11-23 12:52:54 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | STASIS_MESSAGE_TYPE_DEFN(stasis_app_recording_snapshot_type, | 
					
						
							|  |  |  | 	.to_json = recording_to_json, | 
					
						
							|  |  |  | ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 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"; | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 	case STASIS_APP_RECORDING_STATE_CANCELED: | 
					
						
							|  |  |  | 		return "canceled"; | 
					
						
							|  |  |  | 	case STASIS_APP_RECORDING_STATE_MAX: | 
					
						
							|  |  |  | 		return "?"; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-09 12:56:30 +00:00
										 |  |  | 	return AST_RECORD_IF_EXISTS_ERROR; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-23 12:52:54 +00:00
										 |  |  | static void recording_publish(struct stasis_app_recording *recording, const char *cause) | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); | 
					
						
							|  |  |  | 	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_assert(recording != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	json = stasis_app_recording_to_json(recording); | 
					
						
							|  |  |  | 	if (json == NULL) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-23 12:52:54 +00:00
										 |  |  | 	if (!ast_strlen_zero(cause)) { | 
					
						
							|  |  |  | 		struct ast_json *failure_cause = ast_json_string_create(cause); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!failure_cause) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ast_json_object_set(json, "cause", failure_cause)) { | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | static void recording_set_state(struct stasis_app_recording *recording, | 
					
						
							|  |  |  | 				enum stasis_app_recording_state state, | 
					
						
							|  |  |  | 				const char *cause) | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	SCOPED_AO2LOCK(lock, recording); | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 	recording->state = state; | 
					
						
							| 
									
										
										
										
											2013-11-23 12:52:54 +00:00
										 |  |  | 	recording_publish(recording, cause); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | static enum stasis_app_control_channel_result check_rule_recording( | 
					
						
							|  |  |  | 	const struct stasis_app_control *control) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return STASIS_APP_CHANNEL_RECORDING; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-29 14:29:53 -05:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * XXX This only works because there is one and only one rule in | 
					
						
							|  |  |  |  * the system so it can be added to any number of channels | 
					
						
							|  |  |  |  * without issue.  However, as soon as there is another rule then | 
					
						
							|  |  |  |  * watch out for weirdness because of cross linked lists. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static struct stasis_app_control_rule rule_recording = { | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 	.check_rule = check_rule_recording | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void recording_fail(struct stasis_app_control *control, | 
					
						
							|  |  |  | 			   struct stasis_app_recording *recording, | 
					
						
							|  |  |  | 			   const char *cause) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	stasis_app_control_unregister_add_rule(control, &rule_recording); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recording_set_state( | 
					
						
							|  |  |  | 		recording, STASIS_APP_RECORDING_STATE_FAILED, cause); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | static void recording_cleanup(void *data) | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 	struct stasis_app_recording *recording = data; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	ao2_unlink_flags(recordings, recording, | 
					
						
							|  |  |  | 		OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 	ao2_ref(recording, -1); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | static int record_file(struct stasis_app_control *control, | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	struct ast_channel *chan, void *data) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 	struct stasis_app_recording *recording = data; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	char *acceptdtmf; | 
					
						
							|  |  |  | 	int res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_assert(recording != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-13 15:27:32 +00:00
										 |  |  | 	if (stasis_app_get_bridge(control)) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "Cannot record channel while in bridge\n"); | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 		recording_fail(control, recording, "Cannot record channel while in bridge"); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							| 
									
										
										
										
											2013-08-13 15:27:32 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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)); | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 		recording_fail(control, recording, "Failed to answer channel"); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 	recording_set_state( | 
					
						
							|  |  |  | 		recording, STASIS_APP_RECORDING_STATE_RECORDING, NULL); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	ast_play_and_record_full(chan, | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 		NULL, /* playfile */ | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 		recording->absolute_name, | 
					
						
							|  |  |  | 		recording->options->max_duration_seconds, | 
					
						
							|  |  |  | 		recording->options->format, | 
					
						
							| 
									
										
										
										
											2014-07-25 14:47:09 +00:00
										 |  |  | 		&recording->duration.total, | 
					
						
							|  |  |  | 		recording->options->max_silence_seconds ? &recording->duration.energy_only : NULL, | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 		recording->options->beep, | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 		-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)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 	recording_set_state( | 
					
						
							|  |  |  | 		recording, STASIS_APP_RECORDING_STATE_COMPLETE, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	stasis_app_control_unregister_add_rule(control, &rule_recording); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void recording_dtor(void *obj) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct stasis_app_recording *recording = obj; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-03 19:44:58 +00:00
										 |  |  | 	ast_free(recording->absolute_name); | 
					
						
							| 
									
										
										
										
											2016-03-29 13:47:08 -05:00
										 |  |  | 	ao2_cleanup(recording->control); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	ao2_cleanup(recording->options); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct stasis_app_recording *stasis_app_control_record( | 
					
						
							|  |  |  | 	struct stasis_app_control *control, | 
					
						
							|  |  |  | 	struct stasis_app_recording_options *options) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 	struct stasis_app_recording *recording; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	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; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-07-25 14:47:09 +00:00
										 |  |  | 	recording->duration.total = -1; | 
					
						
							|  |  |  | 	recording->duration.energy_only = -1; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ast_asprintf(&recording->absolute_name, "%s/%s", | 
					
						
							|  |  |  | 		ast_config_AST_RECORDING_DIR, options->name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (recording->absolute_name == NULL) { | 
					
						
							|  |  |  | 		errno = ENOMEM; | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 		ao2_ref(recording, -1); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 		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 */ | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 			ao2_ref(recording, -1); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*last_slash = '/'; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(options, +1); | 
					
						
							|  |  |  | 	recording->options = options; | 
					
						
							| 
									
										
										
										
											2016-03-29 13:47:08 -05:00
										 |  |  | 	ao2_ref(control, +1); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	recording->control = control; | 
					
						
							|  |  |  | 	recording->state = STASIS_APP_RECORDING_STATE_QUEUED; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-25 21:28:32 +00:00
										 |  |  | 	if ((recording->options->if_exists == AST_RECORD_IF_EXISTS_FAIL) && | 
					
						
							|  |  |  | 			(ast_fileexists(recording->absolute_name, NULL, NULL))) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Recording file '%s' already exists and ifExists option is failure.\n", | 
					
						
							|  |  |  | 			recording->absolute_name); | 
					
						
							|  |  |  | 		errno = EEXIST; | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 		ao2_ref(recording, -1); | 
					
						
							| 
									
										
										
										
											2013-10-25 21:28:32 +00:00
										 |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	{ | 
					
						
							|  |  |  | 		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; | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 			ao2_ref(recording, -1); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		ao2_link(recordings, recording); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-13 16:38:57 +00:00
										 |  |  | 	stasis_app_control_register_add_rule(control, &rule_recording); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-08-22 16:44:21 +00:00
										 |  |  | 	stasis_app_send_command_async(control, record_file, ao2_bump(recording), recording_cleanup); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-03-29 14:29:53 -05:00
										 |  |  | 	return ao2_find(recordings, name, OBJ_KEY); | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-06 18:20:37 +00:00
										 |  |  | 	json = ast_json_pack("{s: s, s: s, s: s, s: s}", | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 		"name", recording->options->name, | 
					
						
							|  |  |  | 		"format", recording->options->format, | 
					
						
							| 
									
										
										
										
											2014-03-06 18:20:37 +00:00
										 |  |  | 		"state", state_to_string(recording->state), | 
					
						
							|  |  |  | 		"target_uri", recording->options->target); | 
					
						
							| 
									
										
										
										
											2014-07-25 14:47:09 +00:00
										 |  |  | 	if (json && recording->duration.total > -1) { | 
					
						
							|  |  |  | 		ast_json_object_set(json, "duration", | 
					
						
							|  |  |  | 			ast_json_integer_create(recording->duration.total)); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (json && recording->duration.energy_only > -1) { | 
					
						
							|  |  |  | 		ast_json_object_set(json, "talking_duration", | 
					
						
							|  |  |  | 			ast_json_integer_create(recording->duration.energy_only)); | 
					
						
							|  |  |  | 		ast_json_object_set(json, "silence_duration", | 
					
						
							|  |  |  | 			ast_json_integer_create(recording->duration.total - recording->duration.energy_only)); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return ast_json_ref(json); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | typedef int (*recording_operation_cb)(struct stasis_app_recording *recording); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_noop(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_disregard(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	recording->state = STASIS_APP_RECORDING_STATE_CANCELED; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_cancel(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int res = 0; | 
					
						
							|  |  |  | 	recording->state = STASIS_APP_RECORDING_STATE_CANCELED; | 
					
						
							|  |  |  | 	res |= stasis_app_control_queue_control(recording->control, | 
					
						
							|  |  |  | 		AST_CONTROL_RECORD_CANCEL); | 
					
						
							|  |  |  | 	res |= ast_filedelete(recording->absolute_name, NULL); | 
					
						
							|  |  |  | 	return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_stop(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	recording->state = STASIS_APP_RECORDING_STATE_COMPLETE; | 
					
						
							|  |  |  | 	return stasis_app_control_queue_control(recording->control, | 
					
						
							|  |  |  | 		AST_CONTROL_RECORD_STOP); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_pause(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	recording->state = STASIS_APP_RECORDING_STATE_PAUSED; | 
					
						
							|  |  |  | 	return stasis_app_control_queue_control(recording->control, | 
					
						
							|  |  |  | 		AST_CONTROL_RECORD_SUSPEND); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_unpause(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	recording->state = STASIS_APP_RECORDING_STATE_RECORDING; | 
					
						
							|  |  |  | 	return stasis_app_control_queue_control(recording->control, | 
					
						
							|  |  |  | 		AST_CONTROL_RECORD_SUSPEND); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_mute(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (recording->muted) { | 
					
						
							|  |  |  | 		/* already muted */ | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recording->muted = 1; | 
					
						
							|  |  |  | 	return stasis_app_control_queue_control(recording->control, | 
					
						
							|  |  |  | 		AST_CONTROL_RECORD_MUTE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_unmute(struct stasis_app_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!recording->muted) { | 
					
						
							|  |  |  | 		/* already unmuted */ | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return stasis_app_control_queue_control(recording->control, | 
					
						
							|  |  |  | 		AST_CONTROL_RECORD_MUTE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | recording_operation_cb operations[STASIS_APP_RECORDING_STATE_MAX][STASIS_APP_RECORDING_OPER_MAX] = { | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_CANCEL] = recording_disregard, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_STOP] = recording_disregard, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_CANCEL] = recording_cancel, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_STOP] = recording_stop, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_PAUSE] = recording_pause, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNPAUSE] = recording_noop, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_MUTE] = recording_mute, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNMUTE] = recording_unmute, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_CANCEL] = recording_cancel, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_STOP] = recording_stop, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_PAUSE] = recording_noop, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNPAUSE] = recording_unpause, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_MUTE] = recording_mute, | 
					
						
							|  |  |  | 	[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNMUTE] = recording_unmute, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | enum stasis_app_recording_oper_results stasis_app_recording_operation( | 
					
						
							|  |  |  | 	struct stasis_app_recording *recording, | 
					
						
							|  |  |  | 	enum stasis_app_recording_media_operation operation) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 	recording_operation_cb cb; | 
					
						
							|  |  |  | 	SCOPED_AO2LOCK(lock, recording); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-23 15:00:42 +02:00
										 |  |  | 	if ((unsigned int)recording->state >= STASIS_APP_RECORDING_STATE_MAX) { | 
					
						
							| 
									
										
										
										
											2014-05-09 22:49:26 +00:00
										 |  |  | 		ast_log(LOG_WARNING, "Invalid recording state %u\n", | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 			recording->state); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-23 15:00:42 +02:00
										 |  |  | 	if ((unsigned int)operation >= STASIS_APP_RECORDING_OPER_MAX) { | 
					
						
							| 
									
										
										
										
											2014-05-09 22:49:26 +00:00
										 |  |  | 		ast_log(LOG_WARNING, "Invalid recording operation %u\n", | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 			operation); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cb = operations[recording->state][operation]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!cb) { | 
					
						
							|  |  |  | 		if (recording->state != STASIS_APP_RECORDING_STATE_RECORDING) { | 
					
						
							|  |  |  | 			/* So we can be specific in our error message. */ | 
					
						
							|  |  |  | 			return STASIS_APP_RECORDING_OPER_NOT_RECORDING; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			/* And, really, all operations should be valid during
 | 
					
						
							|  |  |  | 			 * recording */ | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, | 
					
						
							| 
									
										
										
										
											2014-05-09 22:49:26 +00:00
										 |  |  | 				"Unhandled operation during recording: %u\n", | 
					
						
							| 
									
										
										
										
											2013-08-06 14:44:45 +00:00
										 |  |  | 				operation); | 
					
						
							|  |  |  | 			return STASIS_APP_RECORDING_OPER_FAILED; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return cb(recording) ? | 
					
						
							|  |  |  | 		STASIS_APP_RECORDING_OPER_FAILED : STASIS_APP_RECORDING_OPER_OK; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int load_module(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int r; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type); | 
					
						
							|  |  |  | 	if (r != 0) { | 
					
						
							| 
									
										
										
										
											2017-04-11 10:07:39 -06:00
										 |  |  | 		return AST_MODULE_LOAD_DECLINE; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash, | 
					
						
							|  |  |  | 		recording_cmp); | 
					
						
							|  |  |  | 	if (!recordings) { | 
					
						
							| 
									
										
										
										
											2017-04-11 10:07:39 -06:00
										 |  |  | 		STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type); | 
					
						
							|  |  |  | 		return AST_MODULE_LOAD_DECLINE; | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis application recording support", | 
					
						
							| 
									
										
										
										
											2014-07-25 16:47:17 +00:00
										 |  |  | 	.support_level = AST_MODULE_SUPPORT_CORE, | 
					
						
							| 
									
										
										
										
											2013-07-03 17:58:45 +00:00
										 |  |  | 	.load = load_module, | 
					
						
							|  |  |  | 	.unload = unload_module, | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	.nonoptreq = "res_stasis", | 
					
						
							|  |  |  | 	.load_pri = AST_MODPRI_APP_DEPEND); |