[svn-commits] ivaxer: branch ivaxer/ast_storage r265569 - in /team/ivaxer/ast_storage: incl...

SVN commits to the Digium repositories svn-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 svn-commits mailing list