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 <mjor...@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 <mjor...@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 -- _____________________________________________________________________ -- Bandwidth and Colocation Provided by http://www.api-digital.com -- svn-commits mailing list To UNSUBSCRIBE or update options visit: http://lists.digium.com/mailman/listinfo/svn-commits