[asterisk-commits] ivaxer: branch ivaxer/ast_storage r265569 - in /team/ivaxer/ast_storage: incl...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Tue May 25 05:52:05 CDT 2010
Author: ivaxer
Date: Tue May 25 05:52:03 2010
New Revision: 265569
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=265569
Log:
Added ast_storage files from old team/group/ast_storage branch.
Added:
team/ivaxer/ast_storage/include/asterisk/storage.h (with props)
team/ivaxer/ast_storage/main/storage.c (with props)
Added: team/ivaxer/ast_storage/include/asterisk/storage.h
URL: http://svnview.digium.com/svn/asterisk/team/ivaxer/ast_storage/include/asterisk/storage.h?view=auto&rev=265569
==============================================================================
--- team/ivaxer/ast_storage/include/asterisk/storage.h (added)
+++ team/ivaxer/ast_storage/include/asterisk/storage.h Tue May 25 05:52:03 2010
@@ -1,0 +1,178 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Digium, Inc.
+ *
+ * Jason Parker <jparker 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 Generic File Storage Engine Support.
+ *
+ * \author Jason Parker <jparker at digium.com>
+ */
+
+#ifndef _ASTERISK_STORAGE_H
+#define _ASTERISK_STORAGE_H
+
+#include "asterisk/linkedlists.h"
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+struct ast_storage_dirent {
+ char name[PATH_MAX];
+ int fd;
+};
+
+/* XXX How are we going to handle metadata about the file?
+ * Is there common metadata that we want/need to store?
+ * examples: duration, time created
+ */
+struct ast_storage_metadata {
+ time_t duration;
+ time_t created;
+};
+
+struct ast_storage_fileinst {
+ struct ast_storage_dirent *ent;
+ AST_RWLIST_ENTRY(ast_storage_fileinst) list;
+};
+
+struct ast_storage_filelist {
+ char name[256];
+ struct ast_storage_metadata *metadata;
+ AST_RWLIST_HEAD(, ast_storage_fileinst) files;
+};
+
+struct ast_storage_be {
+ const char *name;
+ struct ast_storage *(*create)(const char *uri);
+ int (*free)(struct ast_storage *st);
+ int (*get)(struct ast_storage *st, const char *objectname, const char *exts, char *localfile, size_t localfilesize);
+#if 0
+ /* We don't need no stinkin' put() function. */
+ int (*put)(struct ast_storage *st, const char *exts);
+#endif
+ int (*open)(struct ast_storage *st, struct ast_storage_fileinst *fi, mode_t mode);
+ int (*close)(struct ast_storage *st, struct ast_storage_fileinst *fi);
+ void *(*read)(struct ast_storage *st, struct ast_storage_fileinst *fi, size_t chunksize);
+ int (*write)(struct ast_storage *st, struct ast_storage_fileinst *fi, void *data, size_t chunksize);
+ int (*del)(struct ast_storage *st, const char *exts);
+ void *(*opendir)(struct ast_storage *st, const char *objectpath);
+ int (*readdir)(struct ast_storage *st, void *dirptr, struct ast_storage_dirent *dirent);
+ int (*closedir)(struct ast_storage *st, void *dirptr);
+ int (*status)(struct ast_storage *st, int fd);
+ AST_RWLIST_ENTRY(ast_storage_be) list;
+ struct ast_module *module;
+};
+
+/* Most specific backends will have longer structures */
+struct ast_storage {
+ const struct ast_storage_be *be;
+ char path[PATH_MAX];
+ struct ast_storage_filelist *fl;
+ void **xst; /* Backend specific structure */
+};
+
+int __ast_register_storage(const struct ast_storage_be *e, struct ast_module *mod);
+#define ast_register_storage(e) __ast_register_storage(e, ast_module_info->self)
+
+int ast_unregister_storage(const char *name);
+
+int ast_storage_engine_init(void);
+
+/*! \brief Retrieves an instance of a storage method
+ * \param storage_type Name of the backend
+ * \param uri Backend specific URI to the resource
+ */
+struct ast_storage *ast_storage_request(const char *uri);
+
+/*! \brief Releases a storage instance created with ast_storage_request
+ */
+int ast_storage_release(struct ast_storage *st);
+
+/*! \brief Retrieves a file from the storage medium
+ * \param st Storage instance
+ * \param objectname Logical name of the file
+ * \param exts File extensions to retrieve
+ * \param localfile Buffer in which to place the name of the resulting temporary file.
+ * \param localfilesize Length of the above buffer
+ */
+int ast_storage_get(struct ast_storage *st, const char *objectname, const char *exts, char *localfile, size_t localfilesize);
+
+#if 0
+/*! \brief Stores a file into the storage medium
+ * \param st Storage instance
+ * \param exts File extensions to store
+ */
+int ast_storage_put(struct ast_storage *st, const char *exts);
+#endif
+
+int ast_storage_open(struct ast_storage *st, struct ast_storage_fileinst *fi, mode_t mode);
+int ast_storage_close(struct ast_storage *st, struct ast_storage_fileinst *fi);
+void *ast_storage_read(struct ast_storage *st, struct ast_storage_fileinst *fi, size_t chunksize);
+int ast_storage_write(struct ast_storage *st, struct ast_storage_fileinst *fi, void *data, size_t chunksize);
+
+/*! \brief Removes a file from the storage medium
+ * \param st Storage instance
+ * \param exts File extensions to delete
+ */
+int ast_storage_del(struct ast_storage *st, const char *exts);
+
+int ast_storage_copy(struct ast_storage *st, struct ast_storage_fileinst *from, struct ast_storage_fileinst *to);
+
+/*! \brief Starts retrieval of objects in a resource path
+ * \param st Storage instance
+ * \param pathname Logical name of the path
+ * \return Pointer identifying this listing, or NULL on error.
+ */
+void *ast_storage_opendir(struct ast_storage *st, const char *objectname);
+
+/*! \brief Retrieves an objectname from a previously opened path
+ * \param st Storage instance
+ * \param dirptr Pointer returned by ast_storage_opendir
+ * \param dirent Pointer to a storage area for the object
+ */
+int ast_storage_readdir(struct ast_storage *st, void *dirptr, struct ast_storage_dirent *dirent);
+
+/*! \brief Ends retrieval of objects in a resource path
+ * \param st Storage instance
+ * \param dirptr Pointer returned by ast_storage_opendir
+ */
+int ast_storage_closedir(struct ast_storage *st, void *dirptr);
+
+/*! \brief Retrieves a count of objects at a particular path
+ * \param st Storage instance
+ * \param objectname Logical name of the file
+ */
+int ast_storage_count(struct ast_storage *st, const char *objectname);
+
+/*! \brief Get storage engine by name
+ * \param name Name of storage engine
+ */
+struct ast_storage_be *ast_storage_getbyname(const char *name);
+
+/*! \brief Specify a backend specific parameter
+ * \param st Storage instance
+ * \param var Parameter name
+ * \param value Parameter value
+ */
+int ast_storage_parseoptions(struct ast_storage *st, const char *var, const char *value);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_STORAGE_H */
Propchange: team/ivaxer/ast_storage/include/asterisk/storage.h
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: team/ivaxer/ast_storage/include/asterisk/storage.h
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: team/ivaxer/ast_storage/include/asterisk/storage.h
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: team/ivaxer/ast_storage/main/storage.c
URL: http://svnview.digium.com/svn/asterisk/team/ivaxer/ast_storage/main/storage.c?view=auto&rev=265569
==============================================================================
--- team/ivaxer/ast_storage/main/storage.c (added)
+++ team/ivaxer/ast_storage/main/storage.c Tue May 25 05:52:03 2010
@@ -1,0 +1,466 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2007, Digium, Inc.
+ *
+ * Mark Spencer <markster 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 Generic File Format Support.
+ *
+ * \author Mark Spencer <markster at digium.com>
+ */
+
+#include <stdio.h>
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/storage.h"
+#include "asterisk/cli.h"
+#include "asterisk/file.h"
+#include "asterisk/frame.h"
+#include "asterisk/options.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/module.h"
+
+static AST_RWLIST_HEAD_STATIC(storage_engines, ast_storage_be);
+
+struct ast_storage_null {
+ const struct ast_storage_be *be;
+};
+
+static const struct ast_storage_be null_se;
+
+static struct ast_storage *se_create_null(const char *uri)
+{
+ struct ast_storage *st = ast_calloc(1, sizeof(struct ast_storage));
+ struct ast_storage_null *nst = ast_calloc(1, sizeof(struct ast_storage_null));
+ st->xst = (void *)nst;
+ st->be = &null_se;
+ ast_copy_string(st->path, uri, sizeof(st->path));
+ return st;
+}
+
+static int se_free_null(struct ast_storage *st)
+{
+ ast_free(st);
+ return 0;
+}
+
+int __ast_register_storage(const struct ast_storage_be *be, struct ast_module *mod)
+{
+ struct ast_storage_be *tmp;
+
+ if (AST_RWLIST_WRLOCK(&storage_engines)) {
+ ast_log(LOG_WARNING, "Unable to lock storage engine list\n");
+ return -1;
+ }
+ AST_RWLIST_TRAVERSE(&storage_engines, tmp, list) {
+ if (!strcasecmp(be->name, tmp->name)) {
+ AST_RWLIST_UNLOCK(&storage_engines);
+ ast_log(LOG_WARNING, "Tried to register storage engine '%s', already registered\n", be->name);
+ return -1;
+ }
+ }
+ if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
+ AST_RWLIST_UNLOCK(&storage_engines);
+ return -1;
+ }
+ *tmp = *be;
+ tmp->module = mod;
+
+ memset(&tmp->list, 0, sizeof(tmp->list));
+
+ AST_RWLIST_INSERT_TAIL(&storage_engines, tmp, list);
+ AST_RWLIST_UNLOCK(&storage_engines);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Registered storage engine '%s'\n", be->name);
+
+ return 0;
+}
+
+int ast_unregister_storage(const char *name)
+{
+ struct ast_storage_be *tmp;
+ int res = -1;
+
+ if (AST_RWLIST_WRLOCK(&storage_engines)) {
+ ast_log(LOG_WARNING, "Unable to lock storage engine list\n");
+ return -1;
+ }
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&storage_engines, tmp, list) {
+ if (!strcasecmp(name, tmp->name)) {
+ AST_RWLIST_REMOVE_CURRENT(list);
+ ast_free(tmp);
+ res = 0;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END
+ AST_RWLIST_UNLOCK(&storage_engines);
+
+ if (!res) {
+ ast_verb(2, "Unregistered storage engine '%s'\n", name);
+ } else
+ ast_log(LOG_WARNING, "Tried to unregister storage engine '%s', already unregistered\n", name);
+
+ return res;
+}
+
+static int empty_filelist(struct ast_storage_filelist *fl)
+{
+ struct ast_storage_fileinst *fi;
+
+ if (!fl)
+ return 0;
+
+ AST_RWLIST_WRLOCK(&fl->files);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&fl->files, fi, list) {
+ AST_RWLIST_REMOVE_CURRENT(list);
+ ast_free(fi->ent);
+ ast_free(fi);
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END
+ AST_RWLIST_UNLOCK(&fl->files);
+ return 0;
+}
+
+struct ast_storage_be *ast_storage_getbyname(const char *name)
+{
+ struct ast_storage_be *be;
+ if (AST_RWLIST_RDLOCK(&storage_engines)) {
+ ast_log(LOG_WARNING, "Unable to lock storage engine list\n");
+ return NULL;
+ }
+
+ AST_RWLIST_TRAVERSE(&storage_engines, be, list) {
+ if (!strcasecmp(name, be->name))
+ break;
+ }
+ AST_RWLIST_UNLOCK(&storage_engines);
+ return be;
+}
+
+struct ast_storage *ast_storage_request(const char *uri)
+{
+ struct ast_storage_be *be;
+ struct ast_storage *st = NULL;
+ char tmp[PATH_MAX];
+ char *stringp = tmp;
+ char *sename;
+ char *dir;
+
+ stringp = ast_strdupa(uri);
+ if ((dir = strstr(stringp, "://"))) {
+ *dir = '\0';
+ dir += 3;
+ }
+ sename = stringp;
+
+ if (!(ast_storage_getbyname(sename)))
+ return NULL;
+
+ AST_RWLIST_TRAVERSE(&storage_engines, be, list) {
+ if (!strcasecmp(be->name, sename)) {
+ if ((st = be->create(S_OR(dir, ""))))
+ st->fl = ast_calloc(1, sizeof(*st->fl));
+ return st;
+ }
+ }
+ return NULL;
+}
+
+int ast_storage_release(struct ast_storage *st)
+{
+ if (st) {
+ empty_filelist(st->fl);
+ ast_free(st->fl);
+ return st->be->free ? st->be->free(st) : -1;
+ } else
+ return -1;
+}
+
+int ast_storage_get(struct ast_storage *st, const char *objectname, const char *exts, char *localfile, size_t localfilesize)
+{
+ if (st) {
+ /* Empty the file list first, just to be safe */
+ empty_filelist(st->fl);
+ return st->be->get ? st->be->get(st, objectname, exts, localfile, localfilesize) : -1;
+ } else
+ return -1;
+}
+
+#if 0
+int ast_storage_put(struct ast_storage *st, struct ast_storage_filelist *fl, const char *exts)
+{
+ return st && st->be->put ? st->be->put(st, fl, exts) : -1;
+}
+#endif
+
+int ast_storage_open(struct ast_storage *st, struct ast_storage_fileinst *fi, mode_t mode)
+{
+ return st && st->be->open ? st->be->open(st, fi, mode) : -1;
+}
+
+int ast_storage_close(struct ast_storage *st, struct ast_storage_fileinst *fi)
+{
+ return st && st->be->close ? st->be->close(st, fi) : -1;
+}
+
+void *ast_storage_read(struct ast_storage *st, struct ast_storage_fileinst *fi, size_t chunksize)
+{
+ return st && st->be->read ? st->be->read(st, fi, chunksize) : NULL;
+}
+
+int ast_storage_write(struct ast_storage *st, struct ast_storage_fileinst *fi, void *data, size_t chunksize)
+{
+ return st && st->be->write ? st->be->write(st, fi, data, chunksize) : -1;
+}
+
+int ast_storage_del(struct ast_storage *st, const char *exts)
+{
+ return st && st->be->del ? st->be->del(st, exts) : -1;
+}
+
+int ast_storage_copy(struct ast_storage *st, struct ast_storage_fileinst *from, struct ast_storage_fileinst *to)
+{
+ int res = -1;
+ void *data;
+ if (ast_storage_open(st, from, 0) == 0) {
+ if (ast_storage_open(st, to, 0) == 0) {
+ while ((data = ast_storage_read(st, from, 0))) {
+ ast_storage_write(st, to, data, 0);
+ }
+ res = 0;
+ ast_storage_close(st, to);
+ }
+ ast_storage_close(st, from);
+ }
+ return res;
+}
+
+void *ast_storage_opendir(struct ast_storage *st, const char *objectpath)
+{
+ return st->be->opendir ? st->be->opendir(st, objectpath) : NULL;
+}
+
+int ast_storage_readdir(struct ast_storage *st, void *dirptr, struct ast_storage_dirent *dirent)
+{
+ return st->be->readdir ? st->be->readdir(st, dirptr, dirent) : -1;
+}
+
+int ast_storage_closedir(struct ast_storage *st, void *dirptr)
+{
+ return st->be->closedir ? st->be->closedir(st, dirptr) : -1;
+}
+
+int ast_storage_count(struct ast_storage *st, const char *objectpath)
+{
+ void *dirptr;
+ struct ast_storage_dirent dirent;
+ int count = 0;
+
+ if (!(st->be->opendir && st->be->readdir && st->be->closedir))
+ return -1;
+
+ dirptr = st->be->opendir(st, objectpath);
+ if (!dirptr)
+ return -1;
+
+ while (st->be->readdir(st, dirptr, &dirent) == 0) {
+ char *ext;
+
+ /* Parse out the file extension */
+ if ((ext = strrchr(dirent.name, '.'))) {
+ ext++;
+ }
+ if (strcmp(ext, "txt") == 0)
+ count++;
+ }
+
+ st->be->closedir(st, dirptr);
+ return count;
+}
+
+static char *handle_storage_show_file(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_storage *st;
+ struct ast_storage_fileinst *fi;
+ int count_files = 0;
+ /* XXX We want to remove the need to pass in a char array like this now.. */
+ char blah[PATH_MAX];
+ char *fn;
+ char *uri;
+
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "storage show file";
+ e->usage =
+ "Usage: storage show file <engine://filename>\n"
+ " Displays information about a file via the storage engine\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ uri = ast_strdupa(a->argv[3]);
+ if ((fn = strrchr(uri, '/')))
+ *fn++ = '\0';
+ else
+ return CLI_SUCCESS;
+
+ if ((st = ast_storage_request(uri))) {
+ ast_cli(a->fd, "File information\n");
+ /* We don't care about format/extension here, so just send NULL */
+ if (!(ast_storage_get(st, fn, NULL, blah, sizeof(blah)))) {
+ AST_RWLIST_TRAVERSE(&st->fl->files, fi, list) {
+ char *ext;
+
+ /* Parse out the file extension */
+ if ((ext = strrchr(fi->ent->name, '.'))) {
+ ext++;
+ }
+
+ count_files++;
+ ast_cli(a->fd, "Name : %s\n", fi->ent->name);
+ ast_cli(a->fd, "Format: %s\n", ast_getformatname(ast_getformatbyextension(ext)));
+ }
+ }
+ ast_cli(a->fd, "\n");
+ ast_cli(a->fd, "Found %d matching file(s).\n", count_files);
+ }
+ ast_storage_release(st);
+ return CLI_SUCCESS;
+}
+
+static char *handle_storage_show_engines(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_storage_be *be;
+ int count_se = 0;
+
+ switch(cmd) {
+ case CLI_INIT:
+ e->command = "storage show engines";
+ e->usage =
+ "Usage: storage show engines\n"
+ " Displays currently registered storage engines (if any)\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if(a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "Storage Engine\n");
+
+ if (AST_RWLIST_RDLOCK(&storage_engines)) {
+ ast_log(LOG_WARNING, "Unable to lock storage engine list\n");
+ return CLI_FAILURE;
+ }
+
+ AST_RWLIST_TRAVERSE(&storage_engines, be, list) {
+ ast_cli(a->fd, "%s\n", be->name);
+ count_se++;
+ }
+ AST_RWLIST_UNLOCK(&storage_engines);
+ ast_cli(a->fd, "%d storage engine(s) registered.\n", count_se);
+ return CLI_SUCCESS;
+}
+
+static char *complete_storage_show_engines(struct ast_cli_args *a)
+{
+ struct ast_storage_be *be;
+ char *ret = NULL;
+ int which = 0;
+ int wordlen = strlen(a->word);
+
+ AST_RWLIST_RDLOCK(&storage_engines);
+ AST_RWLIST_TRAVERSE(&storage_engines, be, list) {
+ if (!strncasecmp(a->word, be->name, wordlen) && ++which > a->n) {
+ ret = strdup(be->name);
+ break;
+ }
+ }
+ AST_RWLIST_UNLOCK(&storage_engines);
+
+ return ret;
+}
+static char *handle_storage_show_engine(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_storage_be *be;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "storage show engine";
+ e->usage =
+ "Usage: storage show engine <engine>\n"
+ " Displays capabilities of requested storage engine\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_storage_show_engines(a);
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ if ((be = ast_storage_getbyname(a->argv[3]))) {
+ ast_cli(a->fd, "Storage Engine: %s\n", be->name);
+ ast_cli(a->fd, "Can get : %s\n", be->get ? "yes" : "no");
+#if 0
+ ast_cli(a->fd, "Can put : %s\n", be->put ? "yes" : "no");
+#endif
+ ast_cli(a->fd, "Can del : %s\n", be->del ? "yes" : "no");
+ ast_cli(a->fd, "Can opendir : %s\n", be->opendir ? "yes" : "no");
+ ast_cli(a->fd, "Can readdir : %s\n", be->readdir ? "yes" : "no");
+ ast_cli(a->fd, "Can closedir : %s\n", be->closedir ? "yes" : "no");
+ if (be->status) {
+ ast_cli(a->fd, "\n");
+ ast_cli(a->fd, "Additional status\n");
+ /* XXX Need to rethink this.
+ * This requires a struct ast_storage *, and we can't cast to it from ast_storage_be
+ be->status((struct ast_storage *)be, fd);
+ */
+ }
+ } else
+ ast_cli(a->fd, "No storage engine named '%s' found.\n", a->argv[3]);
+ return CLI_SUCCESS;
+}
+
+
+
+struct ast_cli_entry cli_storage[] = {
+ AST_CLI_DEFINE(handle_storage_show_engines,"Show a storage engine"),
+ AST_CLI_DEFINE(handle_storage_show_engine,"List of storage engines"),
+ AST_CLI_DEFINE(handle_storage_show_file,"Show a file within a storage engine"),
+};
+
+static const struct ast_storage_be null_se = {
+ .name = "null",
+ .create = se_create_null,
+ .free = se_free_null,
+};
+
+int ast_storage_engine_init(void)
+{
+ __ast_register_storage(&null_se, NULL);
+ ast_cli_register_multiple(cli_storage, sizeof(cli_storage) / sizeof(struct ast_cli_entry));
+ return 0;
+}
Propchange: team/ivaxer/ast_storage/main/storage.c
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: team/ivaxer/ast_storage/main/storage.c
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: team/ivaxer/ast_storage/main/storage.c
------------------------------------------------------------------------------
svn:mime-type = text/plain
More information about the asterisk-commits
mailing list