[asterisk-commits] mjordan: branch mjordan/trunk-http-stuff-and-things r431402 - in /team/mjorda...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Jan 29 08:38:35 CST 2015


Author: mjordan
Date: Thu Jan 29 08:38:23 2015
New Revision: 431402

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=431402
Log:
Add some initial media caching stuff

- Add the media cache in the core. This acts as a thin wrapper over some bucket
  implementation, which must be provided by other modules. Mostly, it provides
  the actual in-memory cache of the media files and their metadata.
- Add some unit tests for the media cache core.
- Add a skeleton module for the HTTP retrieval of items for the media cache.
  Mostly this just has 'create' callback implemented (although currently it
  has only been compile checked).


Added:
    team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h   (with props)
    team/mjordan/trunk-http-stuff-and-things/main/media_cache.c   (with props)
    team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c   (with props)
    team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c   (with props)
Modified:
    team/mjordan/trunk-http-stuff-and-things/main/asterisk.c
    team/mjordan/trunk-http-stuff-and-things/main/bucket.c
    team/mjordan/trunk-http-stuff-and-things/res/res_curl.c

Added: team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h?view=auto&rev=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h (added)
+++ team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h Thu Jan 29 08:38:23 2015
@@ -1,0 +1,59 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2014, Digium, Inc.
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief AGI Extension interfaces - Asterisk Gateway Interface
+ */
+
+#ifndef _ASTERISK_MEDIA_CACHE_H
+#define _ASTERISK_MEDIA_CACHE_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+struct ast_variable;
+
+/*!
+ * \brief
+ *
+ * \param uri
+ *
+ * \retval 0 uri does not exist in cache
+ * \retval 1 uri does exist in cache
+ */
+int ast_media_cache_exists(const char *uri);
+
+int ast_media_cache_retrieve(const char *uri, const char *preferred_file_name,
+	char *file_path, size_t len);
+
+int ast_media_cache_retrieve_metadata(const char *uri, const char *key,
+	char *value, size_t len);
+
+int ast_media_cache_create_or_update(const char *uri, const char *file_path,
+	struct ast_variable *metadata);
+
+int ast_media_cache_delete(const char *uri);
+
+int ast_media_cache_init(void);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_MEDIA_CACHE_H */

Propchange: team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/mjordan/trunk-http-stuff-and-things/include/asterisk/media_cache.h
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: team/mjordan/trunk-http-stuff-and-things/main/asterisk.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/main/asterisk.c?view=diff&rev=431402&r1=431401&r2=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/main/asterisk.c (original)
+++ team/mjordan/trunk-http-stuff-and-things/main/asterisk.c Thu Jan 29 08:38:23 2015
@@ -250,6 +250,7 @@
 #include "asterisk/endpoints.h"
 #include "asterisk/codec.h"
 #include "asterisk/format_cache.h"
+#include "asterisk/media_cache.h"
 
 #include "../defaults.h"
 
@@ -4469,6 +4470,11 @@
 		exit(1);
 	}
 
+	if (ast_media_cache_init()) {
+		printf("%s", term_quit());
+		exit(1);
+	}
+
 	if ((moduleresult = load_modules(1))) {		/* Load modules, pre-load only */
 		printf("%s", term_quit());
 		exit(moduleresult == -2 ? 2 : 1);

Modified: team/mjordan/trunk-http-stuff-and-things/main/bucket.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/main/bucket.c?view=diff&rev=431402&r1=431401&r2=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/main/bucket.c (original)
+++ team/mjordan/trunk-http-stuff-and-things/main/bucket.c Thu Jan 29 08:38:23 2015
@@ -258,7 +258,7 @@
 
 	if (ast_strlen_zero(name) || !bucket || !file ||
 	    !bucket->create || !bucket->delete || !bucket->retrieve_id ||
-	    !create_cb) {
+	    (!bucket->create && !create_cb)) {
 		return -1;
 	}
 
@@ -679,7 +679,7 @@
 
 	ast_string_field_set(file, scheme, uri_scheme);
 
-	if (scheme->create(file)) {
+	if (scheme->create && scheme->create(file)) {
 		ao2_ref(file, -1);
 		return NULL;
 	}

Added: team/mjordan/trunk-http-stuff-and-things/main/media_cache.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/main/media_cache.c?view=auto&rev=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/main/media_cache.c (added)
+++ team/mjordan/trunk-http-stuff-and-things/main/media_cache.c Thu Jan 29 08:38:23 2015
@@ -1,0 +1,374 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) <Year>, <Your Name Here>
+ *
+ * <Your Name Here> <<Your Email Here>>
+ *
+ * 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 media_cache
+ *
+ * \author \verbatim <Your Name Here> <<Your Email Here>> \endverbatim
+ *
+ * This is a skeleton for development of an Asterisk test module
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/stat.h>
+#include "asterisk/config.h"
+#include "asterisk/bucket.h"
+#include "asterisk/astdb.h"
+#include "asterisk/media_cache.h"
+
+#define AST_DB_FAMILY "MediaCache"
+
+/* Length of 'MediaCache' + 2 '/' characters */
+#define AST_DB_FAMILY_LEN 12
+
+#define AO2_BUCKETS 61
+
+static struct ao2_container *media_cache;
+
+/*! \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_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	case OBJ_KEY:
+		key = obj;
+		return ast_str_hash(key);
+	case OBJ_POINTER:
+		object = obj;
+		return ast_str_hash(ast_sorcery_object_get_id(object));
+	default:
+		/* Hash can only work on something with a full key */
+		ast_assert(0);
+		return 0;
+	}
+}
+
+/*! \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 *name = arg;
+
+	return !strcmp(ast_sorcery_object_get_id(left), flags & OBJ_KEY ?
+		name : ast_sorcery_object_get_id(right)) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+
+int ast_media_cache_exists(const char *uri)
+{
+	struct ast_bucket_file *bucket_file;
+
+	if (ast_strlen_zero(uri)) {
+		return 0;
+	}
+
+	bucket_file = ast_bucket_file_retrieve(uri);
+	if (bucket_file) {
+		ao2_ref(bucket_file, -1);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void metadata_sync_to_astdb(struct ast_bucket_file *bucket_file,
+	const char *hash, const char *name)
+{
+	struct ast_bucket_metadata *metadata;
+
+	metadata = ast_bucket_file_metadata_get(bucket_file, name);
+	if (metadata) {
+		ast_db_put(hash, metadata->name, metadata->value);
+		ao2_ref(metadata, -1);
+	}	
+}
+
+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);
+	metadata_sync_to_astdb(bucket_file, hash, "size");
+	metadata_sync_to_astdb(bucket_file, hash, "ext");
+	metadata_sync_to_astdb(bucket_file, hash, "accessed");
+}
+
+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, hash_value);
+	ast_free(hash_value);
+}
+
+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;
+	}
+
+	bucket_file = ao2_find(media_cache, uri, OBJ_KEY | OBJ_NOLOCK);
+	if (bucket_file) {
+		ao2_lock(bucket_file);
+		ast_bucket_file_update(bucket_file);
+		ast_copy_string(file_path, bucket_file->path, len);
+		media_cache_item_sync_to_astdb(bucket_file);
+		ao2_unlock(bucket_file);
+		ao2_ref(bucket_file, -1);
+		return 0;
+	}
+
+	bucket_file = ast_bucket_file_retrieve(uri);
+	if (!bucket_file) {
+		bucket_file = ast_bucket_file_alloc(uri);
+		if (!bucket_file) {
+			ast_log(LOG_WARNING, "Failed to create storage for %s\n", uri);
+			return -1;
+		}
+
+		if (!ast_strlen_zero(preferred_file_name)) {
+			ast_copy_string(bucket_file->path, preferred_file_name,
+				sizeof(bucket_file->path));
+		} else if (ast_bucket_file_temporary_create(bucket_file)) {
+			ast_log(LOG_WARNING, "Failed to create temp storage for %s\n", uri);
+			ao2_ref(bucket_file, -1);
+			return -1;
+		}
+
+		if (ast_bucket_file_create(bucket_file)) {
+			ast_log(LOG_WARNING, "Failed to obtain media at %s\n", uri);
+			ao2_ref(bucket_file, -1);
+			return -1;
+		}
+	} else if (!ast_strlen_zero(preferred_file_name)) {
+		ao2_lock(bucket_file);
+		rename(bucket_file->path, preferred_file_name);
+		ast_copy_string(bucket_file->path, preferred_file_name,
+			sizeof(bucket_file->path));
+		ao2_unlock(bucket_file);
+	}
+	ast_copy_string(file_path, bucket_file->path, len);
+	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_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_KEY);
+	if (!bucket_file) {
+		return -1;
+	}
+
+	metadata = ao2_find(bucket_file->metadata, key, OBJ_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 stat st;
+	char tmp[128];
+	char *ext;
+	char *file_path_ptr;
+	struct ast_variable *it_metadata;
+	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_KEY | OBJ_NOLOCK);
+	if (!bucket_file) {
+		bucket_file = ast_bucket_file_retrieve(uri);
+		if (!bucket_file) {
+			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;
+			}
+		}
+		ao2_link_flags(media_cache, bucket_file, OBJ_NOLOCK);
+	}
+
+	ao2_lock(bucket_file);
+	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), "%zu", 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 (ast_bucket_file_create(bucket_file)) {
+		ao2_unlock(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_unlock(bucket_file);
+
+	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_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;
+}
+
+static void media_cache_shutdown(void)
+{
+	ao2_ref(media_cache, -1);
+	media_cache = NULL;
+}
+
+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);
+}
+
+static int media_cache_item_populate_from_astdb(const char *uri, const char *hash)
+{
+	char path[PATH_MAX];
+
+	if (ast_db_get(hash, "path", path, sizeof(path))) {
+		ast_log(LOG_WARNING, "Failed to restore media cache item for '%s' from AstDB: no 'path' specified\n",
+			uri);
+		return -1;		
+	}
+
+	/* TODO: do we care about the metadata stored, since we are about to force
+	   a refresh?
+	 */
+	return ast_media_cache_create_or_update(uri + AST_DB_FAMILY_LEN, path, NULL);
+}
+
+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, db_entry->data)) {
+			media_cache_remove_from_astdb(db_entry->key, db_entry->data);
+		}
+	}
+	ast_db_freetree(db_tree);
+}
+
+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;
+	}
+
+	media_cache_populate_from_astdb();
+
+	return 0;
+}

Propchange: team/mjordan/trunk-http-stuff-and-things/main/media_cache.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/mjordan/trunk-http-stuff-and-things/main/media_cache.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/mjordan/trunk-http-stuff-and-things/main/media_cache.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: team/mjordan/trunk-http-stuff-and-things/res/res_curl.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/res/res_curl.c?view=diff&rev=431402&r1=431401&r2=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/res/res_curl.c (original)
+++ team/mjordan/trunk-http-stuff-and-things/res/res_curl.c Thu Jan 29 08:38:23 2015
@@ -51,6 +51,7 @@
 static const char *dependents[] = {
 	"func_curl.so",
 	"res_config_curl.so",
+	"res_http_media_cache.so",
 };
 
 static int unload_module(void)

Added: team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c?view=auto&rev=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c (added)
+++ team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c Thu Jan 29 08:38:23 2015
@@ -1,0 +1,219 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Matt Jordan
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief
+ *
+ * \author\verbatim <Your Name Here> <<Your Email Here>> \endverbatim
+ *
+ * This is a skeleton for development of an Asterisk test module
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+	<depend>curl</depend>
+	<depend>res_curl</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <curl/curl.h>
+
+#include "asterisk/module.h"
+#include "asterisk/bucket.h"
+#include "asterisk/sorcery.h"
+
+#define GLOBAL_USERAGENT "asterisk-libcurl-agent/1.0"
+
+struct curl_bucket_file_data {
+	struct ast_bucket_file *bucket_file;
+	FILE *out_file;
+};
+
+static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)
+{
+	struct curl_bucket_file_data *cb_data = data;
+	size_t realsize;
+	size_t offset;
+	size_t value_len;
+	char *value;
+	char *dupd_value;
+	char *clean_value;
+
+	realsize = size * nitems;
+
+	/* buffer may not be NULL terminated */
+	value = memchr(buffer, ':', realsize);
+	if (!value) {
+		ast_log(LOG_WARNING, "Failed to split received header in cURL request\n");
+		return 0;
+	}
+	offset = value - buffer;
+	value_len = realsize - offset;
+	*value++ = '\0';
+
+	if (strcmp(buffer, "ETag")
+		&& strcmp(buffer, "Cache-Control")
+		&& strcmp(buffer, "Last-Modified")) {
+		return realsize;
+	}
+
+	dupd_value = ast_malloc(value_len + 1);
+	if (!dupd_value) {
+		return 0;
+	}
+	strncpy(dupd_value, value, value_len);
+	dupd_value[value_len] = '\0';
+	clean_value = dupd_value;
+	clean_value = ast_skip_blanks(clean_value);
+	clean_value = ast_trim_blanks(clean_value);
+
+	ast_bucket_file_metadata_set(cb_data->bucket_file, buffer, clean_value);
+
+	ast_free(dupd_value);
+	return realsize;
+}
+
+static size_t curl_body_callback(void *ptr, size_t size, size_t nitems, void *data)
+{
+	struct curl_bucket_file_data *cb_data = data;
+	size_t realsize;
+
+	realsize = fwrite(ptr, size, nitems, cb_data->out_file);
+
+	return realsize;
+}
+
+static int bucket_http_wizard_create(const struct ast_sorcery *sorcery, void *data,
+	void *object)
+{
+	char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
+	struct ast_bucket_file *bucket_file = object;
+	const char *uri = ast_sorcery_object_get_id(bucket_file);
+	CURL *curl;
+	struct curl_bucket_file_data cb_data = {
+		.bucket_file = bucket_file,
+	};
+
+	cb_data.out_file = fopen(bucket_file->path, "wb");
+	if (!cb_data.out_file) {
+		return -1;
+	}
+
+	/* TODO:
+	 * -- force a refresh by pulling down the URI
+	 * -- populate the object
+	 */	
+	curl = curl_easy_init();
+	if (!curl) {
+		fclose(cb_data.out_file);
+		return -1;
+	}
+
+	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 180);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_callback);
+	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
+	curl_easy_setopt(curl, CURLOPT_USERAGENT, GLOBAL_USERAGENT);
+
+	curl_easy_setopt(curl, CURLOPT_URL, uri);
+	curl_easy_setopt(curl, CURLOPT_FILE, (void*)&cb_data);
+	curl_errbuf[CURL_ERROR_SIZE] = '\0';
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
+
+	if (curl_easy_perform(curl)) {
+		ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, uri);
+	}
+
+	fclose(cb_data.out_file);
+	return -1;
+}
+
+static int bucket_http_wizard_update(const struct ast_sorcery *sorcery, void *data,
+	void *object)
+{
+	/*if (!strcmp(ast_sorcery_object_get_id(object), VALID_RESOURCE)) {
+		return 0;
+	}*/
+
+	return -1;
+}
+
+static void *bucket_http_wizard_retrieve_id(const struct ast_sorcery *sorcery,
+	void *data, const char *type, const char *id)
+{
+	/* TODO:
+	 * -- hit the provided URI and see if we need to download it
+	 *   -- if we do, pull it down and update the resource
+	 *   -- if not, simply return what's there
+	 */
+	if (strcmp(type, "file")) {
+		return NULL;
+	}
+	return NULL;
+}
+
+static int bucket_http_wizard_delete(const struct ast_sorcery *sorcery, void *data,
+	void *object)
+{
+	return -1;
+}
+
+static struct ast_sorcery_wizard bucket_wizard = {
+	.name = "http",
+	.create = bucket_http_wizard_create,
+	.retrieve_id = bucket_http_wizard_retrieve_id,
+	.delete = bucket_http_wizard_delete,
+};
+
+static struct ast_sorcery_wizard bucket_file_wizard = {
+	.name = "http",
+	.create = bucket_http_wizard_create,
+	.update = bucket_http_wizard_update,
+	.retrieve_id = bucket_http_wizard_retrieve_id,
+	.delete = bucket_http_wizard_delete,
+};
+
+
+static int unload_module(void)
+{
+	return 0;
+}
+
+static int load_module(void)
+{
+	if (ast_bucket_scheme_register("http", &bucket_wizard, &bucket_file_wizard,
+			NULL, NULL)) {
+		ast_log(LOG_ERROR, "Failed to register Bucket HTTP wizard scheme implementation\n");
+		return AST_MODULE_LOAD_FAILURE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "FOOBAR!",
+		.support_level = AST_MODULE_SUPPORT_CORE,
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_DEFAULT,
+	);
+

Propchange: team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/mjordan/trunk-http-stuff-and-things/res/res_http_media_cache.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c?view=auto&rev=431402
==============================================================================
--- team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c (added)
+++ team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c Thu Jan 29 08:38:23 2015
@@ -1,0 +1,395 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) <Year>, <Your Name Here>
+ *
+ * <Your Name Here> <<Your Email Here>>
+ *
+ * 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 Skeleton Test
+ *
+ * \author\verbatim <Your Name Here> <<Your Email Here>> \endverbatim
+ *
+ * This is a skeleton for development of an Asterisk test module
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/bucket.h"
+#include "asterisk/media_cache.h"
+
+#define CATEGORY "/main/media_cache/"
+
+#define VALID_RESOURCE "httptest://localhost:8088/test_media_cache/monekys.wav"
+
+#define INVALID_RESOURCE "httptest://localhost:8088/test_media_cache/bad.wav"
+
+#define INVALID_SCHEME "foo://localhost:8088/test_media_cache/monkeys.wav"
+
+#define NO_SCHEME "localhost:8088/test_media_cache/monkeys.wav"
+
+static int bucket_http_test_wizard_create(const struct ast_sorcery *sorcery, void *data,
+	void *object)
+{
+	if (!strcmp(ast_sorcery_object_get_id(object), VALID_RESOURCE)) {
+		return 0;
+	}
+
+	return -1;
+}
+
+static int bucket_http_test_wizard_update(const struct ast_sorcery *sorcery, void *data,
+	void *object)
+{
+	if (!strcmp(ast_sorcery_object_get_id(object), VALID_RESOURCE)) {
+		return 0;
+	}
+
+	return -1;
+}
+
+static void *bucket_http_test_wizard_retrieve_id(const struct ast_sorcery *sorcery,
+	void *data, const char *type, const char *id)
+{
+	struct ast_bucket_file *bucket_file;
+
+	if (!strcmp(type, "file") && !strcmp(id, VALID_RESOURCE)) {
+		bucket_file = ast_bucket_file_alloc(id);
+		if (!bucket_file) {
+			return NULL;
+		}
+
+		ast_bucket_file_temporary_create(bucket_file);
+		return bucket_file;
+	}
+	return NULL;
+}
+
+static int bucket_http_test_wizard_delete(const struct ast_sorcery *sorcery, void *data,
+	void *object)
+{
+	if (!strcmp(ast_sorcery_object_get_id(object), VALID_RESOURCE)) {
+		return 0;
+	}
+
+	return -1;
+}
+
+static struct ast_sorcery_wizard bucket_test_wizard = {
+	.name = "httptest",
+	.create = bucket_http_test_wizard_create,
+	.retrieve_id = bucket_http_test_wizard_retrieve_id,
+	.delete = bucket_http_test_wizard_delete,
+};
+
+static struct ast_sorcery_wizard bucket_file_test_wizard = {
+	.name = "httptest",
+	.create = bucket_http_test_wizard_create,
+	.update = bucket_http_test_wizard_update,
+	.retrieve_id = bucket_http_test_wizard_retrieve_id,
+	.delete = bucket_http_test_wizard_delete,
+};
+
+AST_TEST_DEFINE(exists_nominal)
+{
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test nominal existance of resources in the cache";
+		info->description =
+			"This test verifies that if a known resource is in the cache, "
+			"calling ast_media_cache_exists will return logical True. If "
+			"a resource does not exist, the same function call will return "
+			"logical False.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	res = ast_media_cache_exists(INVALID_RESOURCE);
+	ast_test_validate(test, res == 0);
+
+	res = ast_media_cache_exists(VALID_RESOURCE);
+	ast_test_validate(test, res == 1);
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(exists_off_nominal)
+{
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test off nominal existance of resources in the cache";
+		info->description =
+			"This test verifies that checking for bad resources (NULL, bad "
+			"scheme, etc.) does not result in false positivies.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	res = ast_media_cache_exists("");
+	ast_test_validate(test, res != 1);
+
+	res = ast_media_cache_exists(NULL);
+	ast_test_validate(test, res != 1);
+
+	res = ast_media_cache_exists(NO_SCHEME);
+	ast_test_validate(test, res != 1);
+
+	res = ast_media_cache_exists(INVALID_SCHEME);
+	ast_test_validate(test, res != 1);
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(create_update_nominal)
+{
+	int res;
+	char file_path[PATH_MAX];
+	char tmp_path_one[PATH_MAX] = "/tmp/test-media-cache-XXXXXX";
+	char tmp_path_two[PATH_MAX] = "/tmp/test-media-cache-XXXXXX";
+	int fd;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test nominal creation/updating of a resource";
+		info->description =
+			"This test creates a resource and associates it with a file. "
+			"It then updates the resource with a new file. In both cases, "
+			"the test verifies that the resource is associated with the "
+			"file.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Create two local files to associate with a resource */
+	fd = mkstemp(tmp_path_one);
+	if (fd < 0) {
+		ast_test_status_update(test, "Failed to create first tmp file: %s\n",
+			tmp_path_one);
+		return AST_TEST_FAIL;
+	}
+	/* We don't need anything in the file */
+	close(fd);
+
+	fd = mkstemp(tmp_path_two);
+	if (fd < 0) {
+		ast_test_status_update(test, "Failed to create second tmp file: %s\n",
+			tmp_path_two);
+		return AST_TEST_FAIL;
+	}
+	close(fd);
+
+	ast_test_status_update(test, "Creating resource with %s\n", tmp_path_one);
+	res = ast_media_cache_create_or_update(VALID_RESOURCE, tmp_path_one, NULL);
+	ast_test_validate(test, res == 0);
+
+	res = ast_media_cache_retrieve(VALID_RESOURCE, NULL, file_path, PATH_MAX);
+	ast_test_status_update(test, "Got %s for first file path\n", file_path);
+	ast_test_validate(test, res == 0);
+	ast_test_validate(test, strcmp(file_path, tmp_path_one) == 0);
+
+	ast_test_status_update(test, "Creating resource with %s\n", tmp_path_two);
+	res = ast_media_cache_create_or_update(VALID_RESOURCE, tmp_path_two, NULL);
+	ast_test_validate(test, res == 0);
+
+	res = ast_media_cache_retrieve(VALID_RESOURCE, NULL, file_path, PATH_MAX);
+	ast_test_status_update(test, "Got %s for second file path\n", file_path);
+	ast_test_validate(test, res == 0);
+	ast_test_validate(test, strcmp(file_path, tmp_path_two) == 0);
+
+	unlink(tmp_path_one);
+	unlink(tmp_path_two);
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(create_update_off_nominal)
+{
+	int res;
+	char tmp_path[PATH_MAX] = "/tmp/test-media-cache-XXXXXX";
+	int fd;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test off nominal creation/updating of a resource";
+		info->description =
+			"Test creation/updating of a resource with a variety of invalid\n"
+			"inputs.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Create two local files to associate with a resource */
+	fd = mkstemp(tmp_path);
+	if (fd < 0) {
+		ast_test_status_update(test, "Failed to create first tmp file: %s\n",
+			tmp_path);
+		return AST_TEST_FAIL;
+	}
+	/* We don't need anything in the file */
+	close(fd);
+
+	res = ast_media_cache_create_or_update(VALID_RESOURCE, NULL, NULL);
+	ast_test_validate(test, res != 0);
+
+	res = ast_media_cache_create_or_update(VALID_RESOURCE, "", NULL);
+	ast_test_validate(test, res != 0);
+
+	res = ast_media_cache_create_or_update(VALID_RESOURCE, "I don't exist", NULL);
+	ast_test_validate(test, res != 0);
+
+	res = ast_media_cache_create_or_update(INVALID_RESOURCE, tmp_path, NULL);
+	ast_test_validate(test, res != 0);
+
+	res = ast_media_cache_create_or_update(INVALID_SCHEME, tmp_path, NULL);
+	ast_test_validate(test, res != 0);
+
+	res = ast_media_cache_create_or_update(NO_SCHEME, tmp_path, NULL);
+	ast_test_validate(test, res != 0);
+
+	unlink(tmp_path);
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(create_update_metadata)
+{
+	int res;
+	char tmp_path[PATH_MAX] = "/tmp/test-media-cache-XXXXXX";
+	char file_path[PATH_MAX];
+	char actual_metadata[32];
+	struct ast_variable *meta_list = NULL;
+	struct ast_variable *tmp;
+	int fd;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = CATEGORY;
+		info->summary = "Test nominal creation/updating of a resource";
+		info->description =
+			"This test creates a resource and associates it with a file. "
+			"It then updates the resource with a new file. In both cases, "
+			"the test verifies that the resource is associated with the "
+			"file.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Create two local files to associate with a resource */
+	fd = mkstemp(tmp_path);
+	if (fd < 0) {
+		ast_test_status_update(test, "Failed to create first tmp file: %s\n",
+			tmp_path);
+		return AST_TEST_FAIL;
+	}
+	/* We don't need anything in the file */
+	close(fd);
+
+	tmp = ast_variable_new("meta1", "value1", __FILE__);
+	if (!tmp) {
+		ast_test_status_update(test, "Failed to create metadata 1 for test\n");
+		return AST_TEST_FAIL;
+	}
+	ast_variable_list_append(&meta_list, tmp);
+
+	tmp = ast_variable_new("meta2", "value2", __FILE__);
+	if (!tmp) {
+		ast_test_status_update(test, "Failed to create metadata 2 for test\n");
+		return AST_TEST_FAIL;
+	}
+	ast_variable_list_append(&meta_list, tmp);
+
+	res = ast_media_cache_create_or_update(VALID_RESOURCE, tmp_path, meta_list);
+	ast_test_validate(test, res == 0);
+
+	res = ast_media_cache_retrieve(VALID_RESOURCE, NULL, file_path, PATH_MAX);
+	ast_test_status_update(test, "Got %s for second file path\n", file_path);
+	ast_test_validate(test, res == 0);
+	ast_test_validate(test, strcmp(file_path, tmp_path) == 0);
+
+	res = ast_media_cache_retrieve_metadata(VALID_RESOURCE, "meta1",
+		actual_metadata, sizeof(actual_metadata));
+	ast_test_validate(test, res == 0);
+	ast_test_validate(test, strcmp(actual_metadata, "value1") == 0);
+
+	res = ast_media_cache_retrieve_metadata(VALID_RESOURCE, "meta2",
+		actual_metadata, sizeof(actual_metadata));
+	ast_test_validate(test, res == 0);
+	ast_test_validate(test, strcmp(actual_metadata, "value2") == 0);
+
+	unlink(tmp_path);
+
+	return AST_TEST_PASS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(exists_nominal);
+	AST_TEST_UNREGISTER(exists_off_nominal);
+
+	AST_TEST_UNREGISTER(create_update_nominal);
+	AST_TEST_UNREGISTER(create_update_metadata);
+	AST_TEST_UNREGISTER(create_update_off_nominal);
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	if (ast_bucket_scheme_register("httptest", &bucket_test_wizard,
+		&bucket_file_test_wizard, NULL, NULL)) {
+		ast_log(LOG_ERROR, "Failed to register Bucket HTTP test wizard scheme implementation\n");
+		return AST_MODULE_LOAD_FAILURE;
+	}
+
+	AST_TEST_REGISTER(exists_nominal);
+	AST_TEST_REGISTER(exists_off_nominal);
+
+	AST_TEST_REGISTER(create_update_nominal);
+	AST_TEST_REGISTER(create_update_metadata);
+	AST_TEST_REGISTER(create_update_off_nominal);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Media Cache Tests");

Propchange: team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/mjordan/trunk-http-stuff-and-things/tests/test_media_cache.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the asterisk-commits mailing list