| 
									
										
										
										
											2015-01-29 14:38:23 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Asterisk -- An open source telephony toolkit. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2015, Matt Jordan | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Matt Jordan <mjordan@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 An in-memory media cache | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \author \verbatim Matt Jordan <mjordan@digium.com> \endverbatim | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*** MODULEINFO
 | 
					
						
							|  |  |  | 	<support_level>core</support_level> | 
					
						
							|  |  |  |  ***/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ASTERISK_REGISTER_FILE() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <sys/stat.h>
 | 
					
						
							|  |  |  | #include "asterisk/config.h"
 | 
					
						
							|  |  |  | #include "asterisk/bucket.h"
 | 
					
						
							|  |  |  | #include "asterisk/astdb.h"
 | 
					
						
							| 
									
										
										
										
											2015-05-13 16:22:30 -05:00
										 |  |  | #include "asterisk/cli.h"
 | 
					
						
							| 
									
										
										
										
											2015-01-29 14:38:23 +00:00
										 |  |  | #include "asterisk/media_cache.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! The name of the AstDB family holding items in the cache. */ | 
					
						
							|  |  |  | #define AST_DB_FAMILY "MediaCache"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! Length of 'MediaCache' + 2 '/' characters */ | 
					
						
							|  |  |  | #define AST_DB_FAMILY_LEN 12
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! Number of buckets in the ao2 container holding our media items */ | 
					
						
							|  |  |  | #define AO2_BUCKETS 61
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! Our one and only container holding media items */ | 
					
						
							|  |  |  | static struct ao2_container *media_cache; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Hashing function for file metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int media_cache_hash(const void *obj, const int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const struct ast_bucket_file *object; | 
					
						
							|  |  |  | 	const char *key; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (flags & OBJ_SEARCH_MASK) { | 
					
						
							|  |  |  | 	case OBJ_SEARCH_KEY: | 
					
						
							|  |  |  | 		key = obj; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OBJ_SEARCH_OBJECT: | 
					
						
							|  |  |  | 		object = obj; | 
					
						
							|  |  |  | 		key = ast_sorcery_object_get_id(object); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		/* Hash can only work on something with a full key */ | 
					
						
							|  |  |  | 		ast_assert(0); | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ast_str_hash(key); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Comparison function for file metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int media_cache_cmp(void *obj, void *arg, int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *left = obj; | 
					
						
							|  |  |  | 	struct ast_bucket_file *right = arg; | 
					
						
							|  |  |  | 	const char *right_key = arg; | 
					
						
							|  |  |  | 	int cmp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (flags & OBJ_SEARCH_MASK) { | 
					
						
							|  |  |  | 	case OBJ_SEARCH_OBJECT: | 
					
						
							|  |  |  | 		right_key = ast_sorcery_object_get_id(right); | 
					
						
							|  |  |  | 		/* Fall through */ | 
					
						
							|  |  |  | 	case OBJ_SEARCH_KEY: | 
					
						
							|  |  |  | 		cmp = strcmp(ast_sorcery_object_get_id(left), right_key); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OBJ_SEARCH_PARTIAL_KEY: | 
					
						
							|  |  |  | 		cmp = strncmp(ast_sorcery_object_get_id(left), right_key, strlen(right_key)); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		ast_assert(0); | 
					
						
							|  |  |  | 		cmp = 0; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return cmp ? 0 : CMP_MATCH | CMP_STOP; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ast_media_cache_exists(const char *uri) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(uri)) { | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY); | 
					
						
							|  |  |  | 	if (bucket_file) { | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 		return 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Check to see if any bucket implementation could return this item */ | 
					
						
							|  |  |  | 	bucket_file = ast_bucket_file_retrieve(uri); | 
					
						
							|  |  |  | 	if (bucket_file) { | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 		return 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Sync \c bucket_file metadata to the AstDB | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int metadata_sync_to_astdb(void *obj, void *arg, int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_metadata *metadata = obj; | 
					
						
							|  |  |  | 	const char *hash = arg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_db_put(hash, metadata->name, metadata->value); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Sync a media cache item to the AstDB | 
					
						
							|  |  |  |  * \param bucket_file The \c ast_bucket_file media cache item to sync | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void media_cache_item_sync_to_astdb(struct ast_bucket_file *bucket_file) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char hash[41]; /* 40 character SHA1 hash */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_sha1_hash(hash, ast_sorcery_object_get_id(bucket_file)); | 
					
						
							|  |  |  | 	if (ast_db_put(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file), hash)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_db_put(hash, "path", bucket_file->path); | 
					
						
							|  |  |  | 	ast_bucket_file_metadata_callback(bucket_file, metadata_sync_to_astdb, hash); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Delete a media cache item from the AstDB | 
					
						
							|  |  |  |  * \param bucket_file The \c ast_bucket_file media cache item to delete | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void media_cache_item_del_from_astdb(struct ast_bucket_file *bucket_file) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char *hash_value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_db_get_allocated(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file), &hash_value)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_db_deltree(hash_value, NULL); | 
					
						
							| 
									
										
										
										
											2015-05-13 16:22:30 -05:00
										 |  |  | 	ast_db_del(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file)); | 
					
						
							| 
									
										
										
										
											2015-01-29 14:38:23 +00:00
										 |  |  | 	ast_free(hash_value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Update the name of the file backing a \c bucket_file | 
					
						
							|  |  |  |  * \param preferred_file_name The preferred name of the backing file | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void bucket_file_update_path(struct ast_bucket_file *bucket_file, | 
					
						
							|  |  |  | 	const char *preferred_file_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (ast_strlen_zero(preferred_file_name)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!strcmp(bucket_file->path, preferred_file_name)) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rename(bucket_file->path, preferred_file_name); | 
					
						
							|  |  |  | 	ast_copy_string(bucket_file->path, preferred_file_name, | 
					
						
							|  |  |  | 		sizeof(bucket_file->path)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ast_media_cache_retrieve(const char *uri, const char *preferred_file_name, | 
					
						
							|  |  |  | 	char *file_path, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	SCOPED_AO2LOCK(media_lock, media_cache); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(uri)) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* First, retrieve from the ao2 cache here. If we find a bucket_file
 | 
					
						
							|  |  |  | 	 * matching the requested URI, ask the appropriate backend if it is | 
					
						
							|  |  |  | 	 * stale. If not; return it. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK); | 
					
						
							|  |  |  | 	if (bucket_file) { | 
					
						
							|  |  |  | 		if (!ast_bucket_file_is_stale(bucket_file)) { | 
					
						
							|  |  |  | 			ast_copy_string(file_path, bucket_file->path, len); | 
					
						
							|  |  |  | 			ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Stale! Drop the ref, as we're going to retrieve it next. */ | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Either this is new or the resource is stale; do a full retrieve
 | 
					
						
							|  |  |  | 	 * from the appropriate bucket_file backend | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	bucket_file = ast_bucket_file_retrieve(uri); | 
					
						
							|  |  |  | 	if (!bucket_file) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to obtain media at '%s'\n", uri); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* We can manipulate the 'immutable' bucket_file here, as we haven't
 | 
					
						
							|  |  |  | 	 * let anyone know of its existence yet | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	bucket_file_update_path(bucket_file, preferred_file_name); | 
					
						
							|  |  |  | 	media_cache_item_sync_to_astdb(bucket_file); | 
					
						
							|  |  |  | 	ast_copy_string(file_path, bucket_file->path, len); | 
					
						
							|  |  |  | 	ao2_link_flags(media_cache, bucket_file, OBJ_NOLOCK); | 
					
						
							|  |  |  | 	ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ast_media_cache_retrieve_metadata(const char *uri, const char *key, | 
					
						
							|  |  |  | 	char *value, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	struct ast_bucket_metadata *metadata; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(uri) || ast_strlen_zero(key) || !value) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY); | 
					
						
							|  |  |  | 	if (!bucket_file) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	metadata = ao2_find(bucket_file->metadata, key, OBJ_SEARCH_KEY); | 
					
						
							|  |  |  | 	if (!metadata) { | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ast_copy_string(value, metadata->value, len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(metadata, -1); | 
					
						
							|  |  |  | 	ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ast_media_cache_create_or_update(const char *uri, const char *file_path, | 
					
						
							|  |  |  | 	struct ast_variable *metadata) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	struct ast_variable *it_metadata; | 
					
						
							|  |  |  | 	struct stat st; | 
					
						
							|  |  |  | 	char tmp[128]; | 
					
						
							|  |  |  | 	char *ext; | 
					
						
							|  |  |  | 	char *file_path_ptr; | 
					
						
							|  |  |  | 	int created = 0; | 
					
						
							|  |  |  | 	SCOPED_AO2LOCK(media_lock, media_cache); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(file_path) || ast_strlen_zero(uri)) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	file_path_ptr = ast_strdupa(file_path); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (stat(file_path, &st)) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Unable to obtain information for file %s for URI %s\n", | 
					
						
							|  |  |  | 			file_path, uri); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK); | 
					
						
							|  |  |  | 	if (bucket_file) { | 
					
						
							|  |  |  | 		struct ast_bucket_file *clone; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		clone = ast_bucket_file_clone(bucket_file); | 
					
						
							|  |  |  | 		if (!clone) { | 
					
						
							|  |  |  | 			ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Remove the old bucket_file. We'll replace it if we succeed below. */ | 
					
						
							|  |  |  | 		ao2_unlink_flags(media_cache, bucket_file, OBJ_NOLOCK); | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		bucket_file = clone; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		bucket_file = ast_bucket_file_alloc(uri); | 
					
						
							|  |  |  | 		if (!bucket_file) { | 
					
						
							|  |  |  | 			ast_log(LOG_WARNING, "Failed to create file storage for %s and %s\n", | 
					
						
							|  |  |  | 				uri, file_path); | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		created = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	strcpy(bucket_file->path, file_path); | 
					
						
							|  |  |  | 	bucket_file->created.tv_sec = st.st_ctime; | 
					
						
							|  |  |  | 	bucket_file->modified.tv_sec = st.st_mtime; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	snprintf(tmp, sizeof(tmp), "%ld", (long)st.st_atime); | 
					
						
							|  |  |  | 	ast_bucket_file_metadata_set(bucket_file, "accessed", tmp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	snprintf(tmp, sizeof(tmp), "%jd", (intmax_t)st.st_size); | 
					
						
							|  |  |  | 	ast_bucket_file_metadata_set(bucket_file, "size", tmp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ext = strrchr(file_path_ptr, '.'); | 
					
						
							|  |  |  | 	if (ext) { | 
					
						
							|  |  |  | 		ast_bucket_file_metadata_set(bucket_file, "ext", ext + 1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (it_metadata = metadata; it_metadata; it_metadata = it_metadata->next) { | 
					
						
							|  |  |  | 		ast_bucket_file_metadata_set(bucket_file, it_metadata->name, it_metadata->value); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (created && ast_bucket_file_create(bucket_file)) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to create media for %s\n", uri); | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	media_cache_item_sync_to_astdb(bucket_file); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_link_flags(media_cache, bucket_file, OBJ_NOLOCK); | 
					
						
							|  |  |  | 	ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ast_media_cache_delete(const char *uri) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	int res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(uri)) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY | OBJ_UNLINK); | 
					
						
							|  |  |  | 	if (!bucket_file) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res = ast_bucket_file_delete(bucket_file); | 
					
						
							|  |  |  | 	media_cache_item_del_from_astdb(bucket_file); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Remove a media cache item from the AstDB | 
					
						
							|  |  |  |  * \param uri The unique URI that represents the item in the cache | 
					
						
							|  |  |  |  * \param hash The hash key for the item in the AstDB | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void media_cache_remove_from_astdb(const char *uri, const char *hash) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	ast_db_del(AST_DB_FAMILY, uri + AST_DB_FAMILY_LEN); | 
					
						
							|  |  |  | 	ast_db_deltree(hash, NULL); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Create an item in the media cache from entries in the AstDB | 
					
						
							|  |  |  |  * \param uri The unique URI that represents the item in the cache | 
					
						
							|  |  |  |  * \param hash The hash key for the item in the AstDB | 
					
						
							|  |  |  |  * \retval 0 success | 
					
						
							|  |  |  |  * \retval -1 failure | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int media_cache_item_populate_from_astdb(const char *uri, const char *hash) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	struct ast_db_entry *db_tree; | 
					
						
							|  |  |  | 	struct ast_db_entry *db_entry; | 
					
						
							|  |  |  | 	struct stat st; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket_file = ast_bucket_file_alloc(uri); | 
					
						
							|  |  |  | 	if (!bucket_file) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	db_tree = ast_db_gettree(hash, NULL); | 
					
						
							|  |  |  | 	for (db_entry = db_tree; db_entry; db_entry = db_entry->next) { | 
					
						
							|  |  |  | 		const char *key = strchr(db_entry->key + 1, '/'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ast_strlen_zero(key)) { | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		key++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!strcasecmp(key, "path")) { | 
					
						
							|  |  |  | 			strcpy(bucket_file->path, db_entry->data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (stat(bucket_file->path, &st)) { | 
					
						
							|  |  |  | 				ast_log(LOG_WARNING, "Unable to obtain information for file %s for URI %s\n", | 
					
						
							|  |  |  | 					bucket_file->path, uri); | 
					
						
							|  |  |  | 				ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 				ast_db_freetree(db_tree); | 
					
						
							|  |  |  | 				return -1; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ast_bucket_file_metadata_set(bucket_file, key, db_entry->data); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ast_db_freetree(db_tree); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(bucket_file->path)) { | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to restore media cache item for '%s' from AstDB: no 'path' specified\n", | 
					
						
							|  |  |  | 			uri); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_link(media_cache, bucket_file); | 
					
						
							|  |  |  | 	ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Populate the media cache from entries in the AstDB | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void media_cache_populate_from_astdb(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_db_entry *db_entry; | 
					
						
							|  |  |  | 	struct ast_db_entry *db_tree; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	db_tree = ast_db_gettree(AST_DB_FAMILY, NULL); | 
					
						
							|  |  |  | 	for (db_entry = db_tree; db_entry; db_entry = db_entry->next) { | 
					
						
							|  |  |  | 		if (media_cache_item_populate_from_astdb(db_entry->key + AST_DB_FAMILY_LEN, db_entry->data)) { | 
					
						
							|  |  |  | 			media_cache_remove_from_astdb(db_entry->key, db_entry->data); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ast_db_freetree(db_tree); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-13 16:22:30 -05:00
										 |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief ao2 callback function for \ref media_cache_handle_show_all | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int media_cache_prnt_summary(void *obj, void *arg, int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | #define FORMAT_ROW "%-40s\n\t%-40s\n"
 | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file = obj; | 
					
						
							|  |  |  | 	struct ast_cli_args *a = arg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_cli(a->fd, FORMAT_ROW, ast_sorcery_object_get_id(bucket_file), bucket_file->path); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #undef FORMAT_ROW
 | 
					
						
							|  |  |  | 	return CMP_MATCH; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static char *media_cache_handle_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case CLI_INIT: | 
					
						
							|  |  |  | 		e->command = "media cache show all"; | 
					
						
							|  |  |  | 		e->usage = | 
					
						
							|  |  |  | 			"Usage: media cache show all\n" | 
					
						
							|  |  |  | 			"       Display a summary of all current items\n" | 
					
						
							|  |  |  | 			"       in the media cache.\n"; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	case CLI_GENERATE: | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (a->argc != 4) { | 
					
						
							|  |  |  | 		return CLI_SHOWUSAGE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_cli(a->fd, "URI\n\tLocal File\n"); | 
					
						
							|  |  |  | 	ast_cli(a->fd, "---------------\n"); | 
					
						
							|  |  |  | 	ao2_callback(media_cache, OBJ_NODATA | OBJ_MULTIPLE, media_cache_prnt_summary, a); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return CLI_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief CLI tab completion function for URIs | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static char *cli_complete_uri(const char *word, int state) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	struct ao2_iterator it_media_items; | 
					
						
							|  |  |  | 	int wordlen = strlen(word); | 
					
						
							|  |  |  | 	int which = 0; | 
					
						
							|  |  |  | 	char *result = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	it_media_items = ao2_iterator_init(media_cache, 0); | 
					
						
							|  |  |  | 	while ((bucket_file = ao2_iterator_next(&it_media_items))) { | 
					
						
							|  |  |  | 		if (!strncasecmp(word, ast_sorcery_object_get_id(bucket_file), wordlen) | 
					
						
							|  |  |  | 			&& ++which > state) { | 
					
						
							|  |  |  | 			result = ast_strdup(ast_sorcery_object_get_id(bucket_file)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | 		if (result) { | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ao2_iterator_destroy(&it_media_items); | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static char *media_cache_handle_show_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | #define FORMAT_ROW "\t%20s: %-40.40s\n"
 | 
					
						
							|  |  |  | 	struct ast_bucket_file *bucket_file; | 
					
						
							|  |  |  | 	struct ao2_iterator it_metadata; | 
					
						
							|  |  |  | 	struct ast_bucket_metadata *metadata; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case CLI_INIT: | 
					
						
							|  |  |  | 		e->command = "media cache show"; | 
					
						
							|  |  |  | 		e->usage = | 
					
						
							|  |  |  | 			"Usage: media cache show <uri>\n" | 
					
						
							|  |  |  | 			"       Display all information about a particular\n" | 
					
						
							|  |  |  | 			"       item in the media cache.\n"; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	case CLI_GENERATE: | 
					
						
							|  |  |  | 		return cli_complete_uri(a->word, a->n); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (a->argc != 4) { | 
					
						
							|  |  |  | 		return CLI_SHOWUSAGE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket_file = ao2_find(media_cache, a->argv[3], OBJ_SEARCH_KEY); | 
					
						
							|  |  |  | 	if (!bucket_file) { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Unable to find '%s' in the media cache\n", a->argv[3]); | 
					
						
							|  |  |  | 		return CLI_SUCCESS; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_cli(a->fd, "URI: %s\n", ast_sorcery_object_get_id(bucket_file)); | 
					
						
							|  |  |  | 	ast_cli(a->fd, "%s\n", "----------------------------------------"); | 
					
						
							|  |  |  | 	ast_cli(a->fd, FORMAT_ROW, "Path", bucket_file->path); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	it_metadata = ao2_iterator_init(bucket_file->metadata, 0); | 
					
						
							|  |  |  | 	while ((metadata = ao2_iterator_next(&it_metadata))) { | 
					
						
							|  |  |  | 		ast_cli(a->fd, FORMAT_ROW, metadata->name, metadata->value); | 
					
						
							|  |  |  | 		ao2_ref(metadata, -1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ao2_iterator_destroy(&it_metadata); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(bucket_file, -1); | 
					
						
							|  |  |  | #undef FORMAT_ROW
 | 
					
						
							|  |  |  | 	return CLI_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static char *media_cache_handle_delete_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case CLI_INIT: | 
					
						
							|  |  |  | 		e->command = "media cache delete"; | 
					
						
							|  |  |  | 		e->usage = | 
					
						
							|  |  |  | 			"Usage: media cache delete <uri>\n" | 
					
						
							|  |  |  | 			"       Delete an item from the media cache.\n" | 
					
						
							|  |  |  | 			"       Note that this will also remove any local\n" | 
					
						
							|  |  |  | 			"       storage of the media associated with the URI,\n" | 
					
						
							|  |  |  | 			"       and will inform the backend supporting the URI\n" | 
					
						
							|  |  |  | 			"       scheme that it should remove the item.\n"; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	case CLI_GENERATE: | 
					
						
							|  |  |  | 		return cli_complete_uri(a->word, a->n); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (a->argc != 4) { | 
					
						
							|  |  |  | 		return CLI_SHOWUSAGE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_media_cache_delete(a->argv[3])) { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Unable to delete '%s'\n", a->argv[3]); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Deleted '%s' from the media cache\n", a->argv[3]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return CLI_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static char *media_cache_handle_refresh_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char file_path[PATH_MAX]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case CLI_INIT: | 
					
						
							|  |  |  | 		e->command = "media cache refresh"; | 
					
						
							|  |  |  | 		e->usage = | 
					
						
							|  |  |  | 			"Usage: media cache refresh <uri>\n" | 
					
						
							|  |  |  | 			"       Ask for a refresh of a particular URI.\n" | 
					
						
							|  |  |  | 			"       If the item does not already exist in the\n" | 
					
						
							|  |  |  | 			"       media cache, the item will be populated from\n" | 
					
						
							|  |  |  | 			"       the backend supporting the URI scheme.\n"; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	case CLI_GENERATE: | 
					
						
							|  |  |  | 		return cli_complete_uri(a->word, a->n); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (a->argc != 4) { | 
					
						
							|  |  |  | 		return CLI_SHOWUSAGE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_media_cache_retrieve(a->argv[3], NULL, file_path, sizeof(file_path))) { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Unable to refresh '%s'\n", a->argv[3]); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Refreshed '%s' to local storage '%s'\n", a->argv[3], file_path); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return CLI_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static char *media_cache_handle_create_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case CLI_INIT: | 
					
						
							|  |  |  | 		e->command = "media cache create"; | 
					
						
							|  |  |  | 		e->usage = | 
					
						
							|  |  |  | 			"Usage: media cache create <uri> <file>\n" | 
					
						
							|  |  |  | 			"       Create an item in the media cache by associating\n" | 
					
						
							|  |  |  | 			"       a local media file with some URI.\n"; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	case CLI_GENERATE: | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (a->argc != 5) { | 
					
						
							|  |  |  | 		return CLI_SHOWUSAGE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_media_cache_create_or_update(a->argv[3], a->argv[4], NULL)) { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Unable to create '%s' associated with local file '%s'\n", | 
					
						
							|  |  |  | 			a->argv[3], a->argv[4]); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ast_cli(a->fd, "Created '%s' for '%s' in the media cache\n", | 
					
						
							|  |  |  | 			a->argv[3], a->argv[4]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return CLI_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct ast_cli_entry cli_media_cache[] = { | 
					
						
							|  |  |  | 	AST_CLI_DEFINE(media_cache_handle_show_all, "Show all items in the media cache"), | 
					
						
							|  |  |  | 	AST_CLI_DEFINE(media_cache_handle_show_item, "Show a single item in the media cache"), | 
					
						
							|  |  |  | 	AST_CLI_DEFINE(media_cache_handle_delete_item, "Remove an item from the media cache"), | 
					
						
							|  |  |  | 	AST_CLI_DEFINE(media_cache_handle_refresh_item, "Refresh an item in the media cache"), | 
					
						
							|  |  |  | 	AST_CLI_DEFINE(media_cache_handle_create_item, "Create an item in the media cache"), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Shutdown the media cache | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void media_cache_shutdown(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	ao2_ref(media_cache, -1); | 
					
						
							|  |  |  | 	media_cache = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_cli_unregister_multiple(cli_media_cache, ARRAY_LEN(cli_media_cache)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-29 14:38:23 +00:00
										 |  |  | int ast_media_cache_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	ast_register_atexit(media_cache_shutdown); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	media_cache = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, AO2_BUCKETS, | 
					
						
							|  |  |  | 		media_cache_hash, media_cache_cmp); | 
					
						
							|  |  |  | 	if (!media_cache) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-13 16:22:30 -05:00
										 |  |  | 	if (ast_cli_register_multiple(cli_media_cache, ARRAY_LEN(cli_media_cache))) { | 
					
						
							|  |  |  | 		ao2_ref(media_cache, -1); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-29 14:38:23 +00:00
										 |  |  | 	media_cache_populate_from_astdb(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } |