mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	This patch adds five CLI commands for the media cache: * 'media cache show all' - display a summary of all items in the media cache. * 'media cache show <uri>' - display detailed information about a single item in the media cache. * 'media cache delete <uri>' - remove an item from the media cache, and inform the bucket backend for the URI scheme to remove the item as well. * 'media cache refresh <uri>' - refresh a URI. If the item does not exist in the media cache, the bucket backend will pull down the media associated with the URI and create the item in the cache. * 'media cache create <uri>' - create an item in the media cache from some local media storage. Note that the bucket backend for the URI scheme must still permit the item creation. Change-Id: Id1c5707a3b8e2d96b56e4691a46a936cd171f4ae
		
			
				
	
	
		
			710 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			710 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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"
 | |
| #include "asterisk/cli.h"
 | |
| #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);
 | |
| 	ast_db_del(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file));
 | |
| 	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);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \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));
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_cli_register_multiple(cli_media_cache, ARRAY_LEN(cli_media_cache))) {
 | |
| 		ao2_ref(media_cache, -1);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	media_cache_populate_from_astdb();
 | |
| 
 | |
| 	return 0;
 | |
| }
 |