[svn-commits] russell: branch russell/chan_console r49039 - in /team/russell/chan_console: ...

svn-commits at lists.digium.com svn-commits at lists.digium.com
Thu Dec 28 15:04:25 MST 2006


Author: russell
Date: Thu Dec 28 16:04:24 2006
New Revision: 49039

URL: http://svn.digium.com/view/asterisk?view=rev&rev=49039
Log:
Merged revisions 49007-49008,49010,49019,49023,49025-49027,49029-49030,49033,49036 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/trunk

................
r49007 | kpfleming | 2006-12-27 17:10:31 -0500 (Wed, 27 Dec 2006) | 2 lines

add file to ignore list

................
r49008 | kpfleming | 2006-12-27 17:14:33 -0500 (Wed, 27 Dec 2006) | 10 lines

Merged revisions 49006 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r49006 | kpfleming | 2006-12-27 16:06:56 -0600 (Wed, 27 Dec 2006) | 2 lines

since these variables all have static duration, none of them need initializers (they default to zero anyway)

........

................
r49010 | file | 2006-12-27 17:30:28 -0500 (Wed, 27 Dec 2006) | 10 lines

Merged revisions 49009 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r49009 | file | 2006-12-27 17:28:46 -0500 (Wed, 27 Dec 2006) | 2 lines

ast_copy_string is not available when LOW_MEMORY is used and things are being built in the utils directory, so we need to resort to the old method of strncpy. (issue #8579 reported by mottano)

........

................
r49019 | murf | 2006-12-28 12:56:21 -0500 (Thu, 28 Dec 2006) | 1 line

Jason is having problems with the inclusion of <err.h>; it appears to be unnecessary for sucessful builds, so I either removed or commented out the inclusions from all the AEL related code. New outputs from bison/flex are included, etc.
................
r49023 | file | 2006-12-28 14:45:44 -0500 (Thu, 28 Dec 2006) | 9 lines

Blocked revisions 49022 via svnmerge

........
r49022 | file | 2006-12-28 14:43:15 -0500 (Thu, 28 Dec 2006) | 2 lines

Backport support for read/write locks.

........

................
r49025 | qwell | 2006-12-28 14:53:48 -0500 (Thu, 28 Dec 2006) | 9 lines

Blocked revisions 49024 via svnmerge

........
r49024 | qwell | 2006-12-28 13:52:46 -0600 (Thu, 28 Dec 2006) | 2 lines

make the uris_lock a rwlock instead of a mutex lock - needs to be forward ported to trunk

........

................
r49026 | file | 2006-12-28 15:02:41 -0500 (Thu, 28 Dec 2006) | 10 lines

Merged revisions 49024 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r49024 | qwell | 2006-12-28 14:52:46 -0500 (Thu, 28 Dec 2006) | 2 lines

make the uris_lock a rwlock instead of a mutex lock - needs to be forward ported to trunk

........

................
r49027 | file | 2006-12-28 15:05:00 -0500 (Thu, 28 Dec 2006) | 2 lines

Convert uri_redirects list to read/write locks.

................
r49029 | kpfleming | 2006-12-28 15:10:24 -0500 (Thu, 28 Dec 2006) | 10 lines

Merged revisions 49028 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r49028 | kpfleming | 2006-12-28 14:08:59 -0600 (Thu, 28 Dec 2006) | 2 lines

new versions of sounds

........

................
r49030 | tilghman | 2006-12-28 15:13:00 -0500 (Thu, 28 Dec 2006) | 2 lines

Integrate functionality tested on svncommunity users back into trunk

................
r49033 | qwell | 2006-12-28 15:42:28 -0500 (Thu, 28 Dec 2006) | 9 lines

Blocked revisions 49032 via svnmerge

........
r49032 | qwell | 2006-12-28 14:40:23 -0600 (Thu, 28 Dec 2006) | 2 lines

saw this in passing...  fix a small typo

........

................
r49036 | qwell | 2006-12-28 16:28:48 -0500 (Thu, 28 Dec 2006) | 11 lines

Blocked revisions 49035 via svnmerge

........
r49035 | qwell | 2006-12-28 15:26:04 -0600 (Thu, 28 Dec 2006) | 4 lines

Fix some deprecated commands.

Issue 8682, patch by me

........

................

Modified:
    team/russell/chan_console/   (props changed)
    team/russell/chan_console/codecs/g722/   (props changed)
    team/russell/chan_console/configs/func_odbc.conf.sample
    team/russell/chan_console/funcs/func_odbc.c
    team/russell/chan_console/funcs/func_strings.c
    team/russell/chan_console/include/asterisk/ael_structs.h
    team/russell/chan_console/main/ast_expr2.c
    team/russell/chan_console/main/ast_expr2.fl
    team/russell/chan_console/main/ast_expr2.y
    team/russell/chan_console/main/ast_expr2f.c
    team/russell/chan_console/main/asterisk.c
    team/russell/chan_console/main/cdr.c
    team/russell/chan_console/main/channel.c
    team/russell/chan_console/main/dnsmgr.c
    team/russell/chan_console/main/enum.c
    team/russell/chan_console/main/frame.c
    team/russell/chan_console/main/http.c
    team/russell/chan_console/main/logger.c
    team/russell/chan_console/main/manager.c
    team/russell/chan_console/main/pbx.c
    team/russell/chan_console/main/rtp.c
    team/russell/chan_console/main/term.c
    team/russell/chan_console/main/udptl.c
    team/russell/chan_console/pbx/ael/ael.tab.c
    team/russell/chan_console/pbx/ael/ael.tab.h
    team/russell/chan_console/pbx/ael/ael_lex.c
    team/russell/chan_console/sounds/Makefile
    team/russell/chan_console/utils/ael_main.c

Propchange: team/russell/chan_console/
------------------------------------------------------------------------------
Binary property 'branch-1.4-blocked' - no diff available.

Propchange: team/russell/chan_console/
------------------------------------------------------------------------------
Binary property 'branch-1.4-merged' - no diff available.

Propchange: team/russell/chan_console/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Thu Dec 28 16:04:24 2006
@@ -1,1 +1,1 @@
-/trunk:1-49004
+/trunk:1-49038

Propchange: team/russell/chan_console/codecs/g722/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Thu Dec 28 16:04:24 2006
@@ -1,0 +1,1 @@
+libg722.a

Modified: team/russell/chan_console/configs/func_odbc.conf.sample
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/configs/func_odbc.conf.sample?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/configs/func_odbc.conf.sample (original)
+++ team/russell/chan_console/configs/func_odbc.conf.sample Thu Dec 28 16:04:24 2006
@@ -17,25 +17,54 @@
 ; If you have data which may potentially contain single ticks, you may wish
 ; to use the dialplan function SQL_ESC() to escape the data prior to its
 ; inclusion in the SQL statement.
+;
+;
+; The following variables are available in this configuration file:
+;
+; readhandle   A comma-separated list of DSNs (from res_odbc.conf) to use when
+;              executing the readsql statement.  Each DSN is tried, in
+;              succession, until the statement succeeds.  You may specify up to
+;              5 DSNs per function class.  If not specified, it will default to
+;              the value of writehandle or dsn, if specified.
+; writehandle  A comma-separated list of DSNs (from res_odbc.conf) to use when
+;              executing the writesql statement.  The same rules apply as to
+;              readhandle.  "dsn" is a synonym for "writehandle".
+; readsql      The statement to execute when reading from the function class.
+; writesql     The statement to execute when writing to the function class.
+; prefix       Normally, all function classes are prefixed with "ODBC" to keep
+;              them uniquely named.  You may choose to change this prefix, which
+;              may be useful to segregate a collection of certain function
+;              classes from others.
+; escapecommas This option may be used to turn off the default behavior of
+;              escaping commas which occur within a field.  If commas are
+;              escaped (the default behavior), then fields containing commas
+;              will be treated as a single value when assigning to ARRAY() or
+;              HASH().  If commas are not escaped, then values will be separated
+;              at the comma within fields.  Please note that turning this option
+;              off is incompatible with the functionality of HASH().
 
 
 ; ODBC_SQL - Allow an SQL statement to be built entirely in the dialplan
 [SQL]
 dsn=mysql1
-read=${ARG1}
+readsql=${ARG1}
 
 ; ODBC_ANTIGF - A blacklist.
 [ANTIGF]
-dsn=mysql1
-read=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}'
+dsn=mysql1,mysql2   ; Use mysql1 as the primary handle, but fall back to mysql2
+                    ; if mysql1 is down.  Supports up to 5 comma-separated
+                    ; DSNs.  "dsn" may also be specified as "readhandle" and
+                    ; "writehandle", if it is important to separate reads and
+                    ; writes to different databases.
+readsql=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}'
 
 ; ODBC_PRESENCE - Retrieve and update presence
 [PRESENCE]
 dsn=mysql1
-read=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}'
-write=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}'
-;prefix=OFFICE		; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE
-;escapecommas=no	; Normally, commas within a field are escaped such that each
-			; field may be separated into individual variables with ARRAY.
-			; This option turns that behavior off [default=yes].
+readsql=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}'
+writesql=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}'
+;prefix=OFFICE      ; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE
+;escapecommas=no    ; Normally, commas within a field are escaped such that each
+                    ; field may be separated into individual variables with ARRAY.
+                    ; This option turns that behavior off [default=yes].
 

Modified: team/russell/chan_console/funcs/func_odbc.c
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/funcs/func_odbc.c?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/funcs/func_odbc.c (original)
+++ team/russell/chan_console/funcs/func_odbc.c Thu Dec 28 16:04:24 2006
@@ -37,6 +37,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
 
 #include "asterisk/module.h"
 #include "asterisk/file.h"
@@ -57,7 +58,8 @@
 
 struct acf_odbc_query {
 	AST_LIST_ENTRY(acf_odbc_query) list;
-	char dsn[30];
+	char readhandle[5][30];
+	char writehandle[5][30];
 	char sql_read[2048];
 	char sql_write[2048];
 	unsigned int flags;
@@ -96,7 +98,7 @@
 	struct odbc_obj *obj;
 	struct acf_odbc_query *query;
 	char *t, buf[2048]="", varname[15];
-	int i;
+	int i, dsn;
 	AST_DECLARE_APP_ARGS(values,
 		AST_APP_ARG(field)[100];
 	);
@@ -119,14 +121,6 @@
 		return -1;
 	}
 
-	obj = ast_odbc_request_obj(query->dsn, 0);
-
-	if (!obj) {
-		ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", query->dsn);
-		AST_LIST_UNLOCK(&queries);
-		return -1;
-	}
-
 	/* Parse our arguments */
 	t = value ? ast_strdupa(value) : "";
 
@@ -169,7 +163,15 @@
 
 	AST_LIST_UNLOCK(&queries);
 
-	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf);
+	for (dsn = 0; dsn < 5; dsn++) {
+		if (!ast_strlen_zero(query->writehandle[dsn])) {
+			obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
+			if (obj)
+				stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf);
+		}
+		if (stmt)
+			break;
+	}
 
 	if (stmt) {
 		/* Rows affected */
@@ -195,14 +197,15 @@
 {
 	struct odbc_obj *obj;
 	struct acf_odbc_query *query;
-	char sql[2048] = "", varname[15];
-	int res, x, buflen = 0, escapecommas;
+	char sql[2048] = "", varname[15], colnames[2048] = "";
+	int res, x, buflen = 0, escapecommas, dsn;
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(field)[100];
 	);
 	SQLHSTMT stmt;
 	SQLSMALLINT colcount=0;
 	SQLLEN indicator;
+	SQLSMALLINT collength;
 
 	AST_LIST_LOCK(&queries);
 	AST_LIST_TRAVERSE(&queries, query, list) {
@@ -217,14 +220,6 @@
 		return -1;
 	}
 
-	obj = ast_odbc_request_obj(query->dsn, 0);
-
-	if (!obj) {
-		ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn);
-		AST_LIST_UNLOCK(&queries);
-		return -1;
-	}
-
 	AST_STANDARD_APP_ARGS(args, s);
 	for (x = 0; x < args.argc; x++) {
 		snprintf(varname, sizeof(varname), "ARG%d", x + 1);
@@ -244,10 +239,20 @@
 
 	AST_LIST_UNLOCK(&queries);
 
-	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
+	for (dsn = 0; dsn < 5; dsn++) {
+		if (!ast_strlen_zero(query->writehandle[dsn])) {
+			obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
+			if (obj)
+				stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
+		}
+		if (stmt)
+			break;
+	}
 
 	if (!stmt) {
-		ast_odbc_release_obj(obj);
+		ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql);
+		if (obj)
+			ast_odbc_release_obj(obj);
 		return -1;
 	}
 
@@ -278,8 +283,33 @@
 	}
 
 	for (x = 0; x < colcount; x++) {
-		int i;
-		char coldata[256];
+		int i, namelen;
+		char coldata[256], colname[256];
+
+		res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, NULL, NULL, NULL);
+		if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
+			snprintf(colname, sizeof(colname), "field%d", x);
+		}
+
+		if (!ast_strlen_zero(colnames))
+			strncat(colnames, ",", sizeof(colnames) - 1);
+		namelen = strlen(colnames);
+
+		/* Copy data, encoding '\' and ',' for the argument parser */
+		for (i = 0; i < sizeof(colname); i++) {
+			if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) {
+				colnames[namelen++] = '\\';
+			}
+			colnames[namelen++] = colname[i];
+
+			if (namelen >= sizeof(colnames) - 2) {
+				colnames[namelen >= sizeof(colnames) ? sizeof(colnames) - 1 : namelen] = '\0';
+				break;
+			}
+
+			if (colname[i] == '\0')
+				break;
+		}
 
 		buflen = strlen(buf);
 		res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator);
@@ -314,6 +344,8 @@
 	}
 	/* Trim trailing comma */
 	buf[buflen - 1] = '\0';
+
+	pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
 
 	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
 	ast_odbc_release_obj(obj);
@@ -351,27 +383,68 @@
 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
 {
 	const char *tmp;
+	int i;
 
 	if (!cfg || !catg) {
-		return -1;
+		return EINVAL;
 	}
 
 	*query = ast_calloc(1, sizeof(struct acf_odbc_query));
 	if (! (*query))
-		return -1;
-
-	if ((tmp = ast_variable_retrieve(cfg, catg, "dsn"))) {
-		ast_copy_string((*query)->dsn, tmp, sizeof((*query)->dsn));
+		return ENOMEM;
+
+	if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
+		char *tmp2 = ast_strdupa(tmp);
+		AST_DECLARE_APP_ARGS(write,
+			AST_APP_ARG(dsn)[5];
+		);
+		AST_NONSTANDARD_APP_ARGS(write, tmp2, ',');
+		for (i = 0; i < 5; i++) {
+			if (!ast_strlen_zero(write.dsn[i]))
+				ast_copy_string((*query)->writehandle[i], write.dsn[i], sizeof((*query)->writehandle[i]));
+		}
+	}
+
+	if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
+		char *tmp2 = ast_strdupa(tmp);
+		AST_DECLARE_APP_ARGS(read,
+			AST_APP_ARG(dsn)[5];
+		);
+		AST_NONSTANDARD_APP_ARGS(read, tmp2, ',');
+		for (i = 0; i < 5; i++) {
+			if (!ast_strlen_zero(read.dsn[i]))
+				ast_copy_string((*query)->readhandle[i], read.dsn[i], sizeof((*query)->readhandle[i]));
+		}
 	} else {
-		return -1;
-	}
+		/* If no separate readhandle, then use the writehandle for reading */
+		for (i = 0; i < 5; i++) {
+			if (!ast_strlen_zero((*query)->writehandle[i]))
+				ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
+		}
+ 	}
 
 	if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
+		ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s.  Please use 'readsql' instead.\n", catg);
 		ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
+	} else if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")))
+		ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
+
+	if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) {
+		free(*query);
+		ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
+		return EINVAL;
 	}
 
 	if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
+		ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s.  Please use 'writesql' instead.\n", catg);
 		ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
+	} else if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")))
+		ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
+
+	if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) {
+		free(*query);
+		ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
+		return EINVAL;
 	}
 
 	/* Allow escaping of embedded commas in fields to be turned off */
@@ -384,7 +457,7 @@
 	(*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
 	if (! (*query)->acf) {
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
@@ -396,7 +469,7 @@
 	if (!((*query)->acf->name)) {
 		free((*query)->acf);
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
@@ -405,7 +478,7 @@
 		free((char *)(*query)->acf->name);
 		free((*query)->acf);
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	(*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
@@ -432,15 +505,21 @@
 					"${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
 					"This function may only be set.\nSQL:\n%s\n",
 					(*query)->sql_write);
-	}
-
-	/* Could be out of memory, or could be we have neither sql_read nor sql_write */
+	} else {
+		free((char *)(*query)->acf->syntax);
+		free((char *)(*query)->acf->name);
+		free((*query)->acf);
+		free(*query);
+		ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute.  Ignoring.\n", catg);
+		return EINVAL;
+	}
+
 	if (! ((*query)->acf->desc)) {
 		free((char *)(*query)->acf->syntax);
 		free((char *)(*query)->acf->name);
 		free((*query)->acf);
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	if (ast_strlen_zero((*query)->sql_read)) {
@@ -475,7 +554,7 @@
 	return 0;
 }
 
-static int odbc_load_module(void)
+static int load_module(void)
 {
 	int res = 0;
 	struct ast_config *cfg;
@@ -494,10 +573,15 @@
 	     catg;
 	     catg = ast_category_browse(cfg, catg)) {
 		struct acf_odbc_query *query = NULL;
-
-		if (init_acf_query(cfg, catg, &query)) {
-			ast_log(LOG_ERROR, "Out of memory\n");
-			free_acf_query(query);
+		int err;
+
+		if ((err = init_acf_query(cfg, catg, &query))) {
+			if (err == ENOMEM)
+				ast_log(LOG_ERROR, "Out of memory\n");
+			else if (err == EINVAL)
+				ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
+			else
+				ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
 		} else {
 			AST_LIST_INSERT_HEAD(&queries, query, list);
 			ast_custom_function_register(query->acf);
@@ -505,15 +589,16 @@
 	}
 
 	ast_config_destroy(cfg);
-	ast_custom_function_register(&escape_function);
+	res |= ast_custom_function_register(&escape_function);
 
 	AST_LIST_UNLOCK(&queries);
 	return res;
 }
 
-static int odbc_unload_module(void)
+static int unload_module(void)
 {
 	struct acf_odbc_query *query;
+	int res = 0;
 
 	AST_LIST_LOCK(&queries);
 	while (!AST_LIST_EMPTY(&queries)) {
@@ -522,10 +607,11 @@
 		free_acf_query(query);
 	}
 
-	ast_custom_function_unregister(&escape_function);
+	res |= ast_custom_function_unregister(&escape_function);
 
 	/* Allow any threads waiting for this lock to pass (avoids a race) */
 	AST_LIST_UNLOCK(&queries);
+	usleep(1);
 	AST_LIST_LOCK(&queries);
 
 	AST_LIST_UNLOCK(&queries);
@@ -572,16 +658,6 @@
 	return res;
 }
 
-static int unload_module(void)
-{
-	return odbc_unload_module();
-}
-
-static int load_module(void)
-{
-	return odbc_load_module();
-}
-
 /* XXX need to revise usecount - set if query_lock is set */
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",

Modified: team/russell/chan_console/funcs/func_strings.c
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/funcs/func_strings.c?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/funcs/func_strings.c (original)
+++ team/russell/chan_console/funcs/func_strings.c Thu Dec 28 16:04:24 2006
@@ -155,6 +155,37 @@
 	.read = regex,
 };
 
+#define HASH_PREFIX	"~HASH~%s~"
+#define HASH_FORMAT	HASH_PREFIX "%s~"
+
+static char *app_clearhash = "ClearHash";
+static char *syn_clearhash = "Clear the keys from a specified hashname";
+static char *desc_clearhash =
+"ClearHash(<hashname>)\n"
+"  Clears all keys out of the specified hashname\n";
+
+/* This function probably should migrate to main/pbx.c, as pbx_builtin_clearvar_prefix() */
+static void clearvar_prefix(struct ast_channel *chan, const char *prefix)
+{
+	struct ast_var_t *var;
+	int len = strlen(prefix);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->varshead, var, entries) {
+		if (strncasecmp(prefix, ast_var_name(var), len) == 0) {
+			AST_LIST_REMOVE_CURRENT(&chan->varshead, entries);
+			free(var);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END
+}
+
+static int exec_clearhash(struct ast_channel *chan, void *data)
+{
+	char prefix[80];
+	snprintf(prefix, sizeof(prefix), HASH_PREFIX, data ? (char *)data : "null");
+	clearvar_prefix(chan, prefix);
+	return 0;
+}
+
 static int array(struct ast_channel *chan, char *cmd, char *var,
 		 const char *value)
 {
@@ -164,12 +195,22 @@
 	AST_DECLARE_APP_ARGS(arg2,
 			     AST_APP_ARG(val)[100];
 	);
-	char *value2;
-	int i;
+	char *origvar = "", *value2, varname[256];
+	int i, ishash = 0;
 
 	value2 = ast_strdupa(value);
 	if (!var || !value2)
 		return -1;
+
+	if (!strcmp(cmd, "HASH")) {
+		const char *var2 = pbx_builtin_getvar_helper(chan, "~ODBCFIELDS~");
+		origvar = var;
+		if (var2)
+			var = ast_strdupa(var2);
+		else
+			return -1;
+		ishash = 1;
+	}
 
 	/* The functions this will generally be used with are SORT and ODBC_*, which
 	 * both return comma-delimited lists.  However, if somebody uses literal lists,
@@ -194,16 +235,138 @@
 			ast_log(LOG_DEBUG, "array set value (%s=%s)\n", arg1.var[i],
 				arg2.val[i]);
 		if (i < arg2.argc) {
-			pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]);
+			if (ishash) {
+				snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
+				pbx_builtin_setvar_helper(chan, varname, arg2.val[i]);
+			} else {
+				pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]);
+			}
 		} else {
 			/* We could unset the variable, by passing a NULL, but due to
 			 * pushvar semantics, that could create some undesired behavior. */
-			pbx_builtin_setvar_helper(chan, arg1.var[i], "");
+			if (ishash) {
+				snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
+				pbx_builtin_setvar_helper(chan, varname, "");
+			} else {
+				pbx_builtin_setvar_helper(chan, arg1.var[i], "");
+			}
 		}
 	}
 
 	return 0;
 }
+
+static int hashkeys_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+	struct ast_var_t *newvar;
+	int plen;
+	char prefix[80];
+	snprintf(prefix, sizeof(prefix), HASH_PREFIX, data);
+	plen = strlen(prefix);
+
+	memset(buf, 0, len);
+	AST_LIST_TRAVERSE(&chan->varshead, newvar, entries) {
+		if (strncasecmp(prefix, ast_var_name(newvar), plen) == 0) {
+			/* Copy everything after the prefix */
+			strncat(buf, ast_var_name(newvar) + plen, len);
+			/* Trim the trailing ~ */
+			buf[strlen(buf) - 1] = ',';
+		}
+	}
+	/* Trim the trailing comma */
+	buf[strlen(buf) - 1] = '\0';
+	return 0;
+}
+
+static int hash_write(struct ast_channel *chan, char *cmd, char *var, const char *value)
+{
+	char varname[256];
+	AST_DECLARE_APP_ARGS(arg,
+		AST_APP_ARG(hashname);
+		AST_APP_ARG(hashkey);
+	);
+
+	if (!strchr(var, '|')) {
+		/* Single argument version */
+		return array(chan, "HASH", var, value);
+	}
+
+	AST_STANDARD_APP_ARGS(arg, var);
+	snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
+	pbx_builtin_setvar_helper(chan, varname, value);
+
+	return 0;
+}
+
+static int hash_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+	char varname[256];
+	const char *varvalue;
+	AST_DECLARE_APP_ARGS(arg,
+		AST_APP_ARG(hashname);
+		AST_APP_ARG(hashkey);
+	);
+
+	AST_STANDARD_APP_ARGS(arg, data);
+	if (arg.argc == 2) {
+		snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
+		varvalue = pbx_builtin_getvar_helper(chan, varname);
+		if (varvalue)
+			ast_copy_string(buf, varvalue, len);
+		else
+			*buf = '\0';
+	} else if (arg.argc == 1) {
+		char colnames[4096];
+		int i;
+		AST_DECLARE_APP_ARGS(arg2,
+			AST_APP_ARG(col)[100];
+		);
+
+		/* Get column names, in no particular order */
+		hashkeys_read(chan, "HASHKEYS", arg.hashname, colnames, sizeof(colnames));
+		pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
+
+		AST_NONSTANDARD_APP_ARGS(arg2, colnames, ',');
+		*buf = '\0';
+
+		/* Now get the corresponding column values, in exactly the same order */
+		for (i = 0; i < arg2.argc; i++) {
+			snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg2.col[i]);
+			varvalue = pbx_builtin_getvar_helper(chan, varname);
+			strncat(buf, varvalue, len);
+			strncat(buf, ",", len);
+		}
+
+		/* Strip trailing comma */
+		buf[strlen(buf) - 1] = '\0';
+	}
+
+	return 0;
+}
+
+static struct ast_custom_function hash_function = {
+	.name = "HASH",
+	.synopsis = "Implementation of a dialplan associative array",
+	.syntax = "HASH(hashname[|hashkey])",
+	.write = hash_write,
+	.read = hash_read,
+	.desc =
+		"In two argument mode, gets and sets values to corresponding keys within a named\n"
+		"associative array.  The single-argument mode will only work when assigned to from\n"
+		"a function defined by func_odbc.so.\n",
+};
+
+static struct ast_custom_function hashkeys_function = {
+	.name = "HASHKEYS",
+	.synopsis = "Retrieve the keys of a HASH()",
+	.syntax = "HASHKEYS(<hashname>)",
+	.read = hashkeys_read,
+	.desc =
+		"Returns a comma-delimited list of the current keys of an associative array\n"
+	   	"defined by the HASH() function.  Note that if you iterate over the keys of\n"
+		"the result, adding keys during iteration will cause the result of the HASHKEYS\n"
+		"function to change.\n",
+};
 
 static struct ast_custom_function array_function = {
 	.name = "ARRAY",
@@ -589,6 +752,9 @@
 	res |= ast_custom_function_unregister(&eval_function);
 	res |= ast_custom_function_unregister(&keypadhash_function);
 	res |= ast_custom_function_unregister(&sprintf_function);
+	res |= ast_custom_function_unregister(&hashkeys_function);
+	res |= ast_custom_function_unregister(&hash_function);
+	res |= ast_unregister_application(app_clearhash);
 
 	return res;
 }
@@ -608,6 +774,9 @@
 	res |= ast_custom_function_register(&eval_function);
 	res |= ast_custom_function_register(&keypadhash_function);
 	res |= ast_custom_function_register(&sprintf_function);
+	res |= ast_custom_function_register(&hashkeys_function);
+	res |= ast_custom_function_register(&hash_function);
+	res |= ast_register_application(app_clearhash, exec_clearhash, syn_clearhash, desc_clearhash);
 
 	return res;
 }

Modified: team/russell/chan_console/include/asterisk/ael_structs.h
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/include/asterisk/ael_structs.h?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/include/asterisk/ael_structs.h (original)
+++ team/russell/chan_console/include/asterisk/ael_structs.h Thu Dec 28 16:04:24 2006
@@ -2,7 +2,7 @@
 #define _ASTERISK_AEL_STRUCTS_H
 
 #if !defined(SOLARIS) && !defined(__CYGWIN__)
-#include <err.h>
+/* #include <err.h> */
 #else
 #define quad_t int64_t
 #endif

Modified: team/russell/chan_console/main/ast_expr2.c
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/main/ast_expr2.c?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/main/ast_expr2.c (original)
+++ team/russell/chan_console/main/ast_expr2.c Thu Dec 28 16:04:24 2006
@@ -153,7 +153,7 @@
 #include <unistd.h>
 #include <ctype.h>
 #if !defined(SOLARIS) && !defined(__CYGWIN__)
-#include <err.h>
+	/* #include <err.h> */
 #else
 #define quad_t int64_t
 #endif

Modified: team/russell/chan_console/main/ast_expr2.fl
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/main/ast_expr2.fl?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/main/ast_expr2.fl (original)
+++ team/russell/chan_console/main/ast_expr2.fl Thu Dec 28 16:04:24 2006
@@ -35,7 +35,7 @@
 #include <locale.h>
 #include <ctype.h>
 #if !defined(SOLARIS) && !defined(__CYGWIN__)
-#include <err.h>
+/* #include <err.h> */
 #else
 #define quad_t int64_t
 #endif

Modified: team/russell/chan_console/main/ast_expr2.y
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/main/ast_expr2.y?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/main/ast_expr2.y (original)
+++ team/russell/chan_console/main/ast_expr2.y Thu Dec 28 16:04:24 2006
@@ -26,7 +26,7 @@
 #include <unistd.h>
 #include <ctype.h>
 #if !defined(SOLARIS) && !defined(__CYGWIN__)
-#include <err.h>
+	/* #include <err.h> */
 #else
 #define quad_t int64_t
 #endif

Modified: team/russell/chan_console/main/ast_expr2f.c
URL: http://svn.digium.com/view/asterisk/team/russell/chan_console/main/ast_expr2f.c?view=diff&rev=49039&r1=49038&r2=49039
==============================================================================
--- team/russell/chan_console/main/ast_expr2f.c (original)
+++ team/russell/chan_console/main/ast_expr2f.c Thu Dec 28 16:04:24 2006
@@ -9,7 +9,7 @@
 #define FLEX_SCANNER
 #define YY_FLEX_MAJOR_VERSION 2
 #define YY_FLEX_MINOR_VERSION 5
-#define YY_FLEX_SUBMINOR_VERSION 33
+#define YY_FLEX_SUBMINOR_VERSION 31
 #if YY_FLEX_SUBMINOR_VERSION > 0
 #define FLEX_BETA
 #endif
@@ -31,15 +31,7 @@
 
 /* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
 
-#if __STDC_VERSION__ >= 199901L
-
-/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
- * if you want the limit (max/min) macros for int types. 
- */
-#ifndef __STDC_LIMIT_MACROS
-#define __STDC_LIMIT_MACROS 1
-#endif
-
+#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
 #include <inttypes.h>
 typedef int8_t flex_int8_t;
 typedef uint8_t flex_uint8_t;
@@ -161,10 +153,6 @@
 #ifndef YY_BUF_SIZE
 #define YY_BUF_SIZE 16384
 #endif
-
-/* The state buf must be large enough to hold one state per character in the main buffer.
- */
-#define YY_STATE_BUF_SIZE   ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
 
 #ifndef YY_TYPEDEF_YY_BUFFER_STATE
 #define YY_TYPEDEF_YY_BUFFER_STATE
@@ -1367,7 +1355,7 @@
 #include <locale.h>
 #include <ctype.h>
 #if !defined(SOLARIS) && !defined(__CYGWIN__)
-#include <err.h>
+/* #include <err.h> */
 #else
 #define quad_t int64_t
 #endif
@@ -1423,19 +1411,17 @@
 static int curlycount = 0;
 static char *expr2_token_subst(char *mess);
 
-#line 1427 "ast_expr2f.c"
+#line 1415 "ast_expr2f.c"
 
 #define INITIAL 0
 #define var 1
 #define trail 2
 
-#ifndef YY_NO_UNISTD_H
 /* Special case for "unistd.h", since it is non-ANSI. We include it way
  * down here because we want the user's section 1 to have been scanned first.
  * The user has a chance to override it with an option.
  */
 #include <unistd.h>
-#endif
 
 #ifndef YY_EXTRA_TYPE
 #define YY_EXTRA_TYPE void *
@@ -1479,8 +1465,6 @@
 
     }; /* end struct yyguts_t */
 
-static int yy_init_globals (yyscan_t yyscanner );
-
     /* This must go here because YYSTYPE and YYLTYPE are included
      * from bison output in section 1.*/
     #    define yylval yyg->yylval_r
@@ -1615,11 +1599,9 @@
 #ifndef YY_DECL
 #define YY_DECL_IS_OURS 1
 
-extern int ast_yylex \
-               (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner);
-
-#define YY_DECL int ast_yylex \
-               (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner)
+extern int ast_yylex (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner);
+
+#define YY_DECL int ast_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner)
 #endif /* !YY_DECL */
 
 /* Code executed at the beginning of each rule, after yytext and yyleng
@@ -1649,15 +1631,15 @@
 #line 104 "ast_expr2.fl"
 
 
-#line 1653 "ast_expr2f.c"
+#line 1635 "ast_expr2f.c"
 
     yylval = yylval_param;
 
     yylloc = yylloc_param;
 
-	if ( !yyg->yy_init )
+	if ( yyg->yy_init )
 		{
-		yyg->yy_init = 1;
+		yyg->yy_init = 0;
 
 #ifdef YY_USER_INIT
 		YY_USER_INIT;
@@ -1966,7 +1948,7 @@
 #line 205 "ast_expr2.fl"
 ECHO;
 	YY_BREAK
-#line 1970 "ast_expr2f.c"
+#line 1952 "ast_expr2f.c"
 case YY_STATE_EOF(INITIAL):
 case YY_STATE_EOF(var):
 	yyterminate();
@@ -2154,7 +2136,7 @@
 
 	else
 		{
-			int num_to_read =
+			size_t num_to_read =
 			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
 
 		while ( num_to_read <= 0 )
@@ -2199,7 +2181,7 @@
 
 		/* Read in more data. */
 		YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
-			yyg->yy_n_chars, (size_t) num_to_read );
+			yyg->yy_n_chars, num_to_read );
 
 		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
 		}
@@ -2268,7 +2250,7 @@
     static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state , yyscan_t yyscanner)
 {
 	register int yy_is_jam;
-    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
 	register char *yy_cp = yyg->yy_c_buf_p;
 
 	yy_current_state = yy_NUL_trans[yy_current_state];
@@ -2708,26 +2690,26 @@
 
 /** Setup the input buffer state to scan a string. The next call to ast_yylex() will
  * scan from a @e copy of @a str.
- * @param yystr a NUL-terminated string to scan
+ * @param str a NUL-terminated string to scan
  * @param yyscanner The scanner object.
  * @return the newly allocated buffer state object.
  * @note If you want to scan bytes that may contain NUL values, then use
  *       ast_yy_scan_bytes() instead.
  */
-YY_BUFFER_STATE ast_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner)
+YY_BUFFER_STATE ast_yy_scan_string (yyconst char * str , yyscan_t yyscanner)
 {
     
-	return ast_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner);
+	return ast_yy_scan_bytes(str,strlen(str) ,yyscanner);
 }
 
 /** Setup the input buffer state to scan the given bytes. The next call to ast_yylex() will
  * scan from a @e copy of @a bytes.
- * @param yybytes the byte buffer to scan
- * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
  * @param yyscanner The scanner object.
  * @return the newly allocated buffer state object.
  */
-YY_BUFFER_STATE ast_yy_scan_bytes  (yyconst char * yybytes, int  _yybytes_len , yyscan_t yyscanner)
+YY_BUFFER_STATE ast_yy_scan_bytes  (yyconst char * bytes, int  len , yyscan_t yyscanner)
 {
 	YY_BUFFER_STATE b;
 	char *buf;
@@ -2735,15 +2717,15 @@
 	int i;
     
 	/* Get memory for full buffer, including space for trailing EOB's. */
-	n = _yybytes_len + 2;
+	n = len + 2;
 	buf = (char *) ast_yyalloc(n ,yyscanner );
 	if ( ! buf )
 		YY_FATAL_ERROR( "out of dynamic memory in ast_yy_scan_bytes()" );
 
-	for ( i = 0; i < _yybytes_len; ++i )
-		buf[i] = yybytes[i];
-
-	buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+	for ( i = 0; i < len; ++i )
+		buf[i] = bytes[i];
+
+	buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR;
 
 	b = ast_yy_scan_buffer(buf,n ,yyscanner);
 	if ( ! b )
@@ -2884,7 +2866,7 @@
 }
 
 /** Set the current column.
- * @param column_no
+ * @param line_number
  * @param yyscanner The scanner object.
  */
 void ast_yyset_column (int  column_no , yyscan_t yyscanner)
@@ -2954,51 +2936,21 @@
     yylloc = yylloc_param;
 }
     
-/* User-visible API */
-
-/* ast_yylex_init is special because it creates the scanner itself, so it is
- * the ONLY reentrant function that doesn't take the scanner as the last argument.
- * That's why we explicitly handle the declaration, instead of using our macros.
- */
-
-int ast_yylex_init(yyscan_t* ptr_yy_globals)
-
-{
-    if (ptr_yy_globals == NULL){
-        errno = EINVAL;
-        return 1;
-    }
-
-    *ptr_yy_globals = (yyscan_t) ast_yyalloc ( sizeof( struct yyguts_t ), NULL );
-
-    if (*ptr_yy_globals == NULL){
-        errno = ENOMEM;
-        return 1;
-    }
-
-    /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */
-    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
-
-    return yy_init_globals ( *ptr_yy_globals );
-}
-
 static int yy_init_globals (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
     /* Initialization is the same as for the non-reentrant scanner.
-     * This function is called from ast_yylex_destroy(), so don't allocate here.
-     */
+       This function is called once per scanner lifetime. */
 
     yyg->yy_buffer_stack = 0;
     yyg->yy_buffer_stack_top = 0;
     yyg->yy_buffer_stack_max = 0;
     yyg->yy_c_buf_p = (char *) 0;
-    yyg->yy_init = 0;
+    yyg->yy_init = 1;
     yyg->yy_start = 0;
-
     yyg->yy_start_stack_ptr = 0;
     yyg->yy_start_stack_depth = 0;
-    yyg->yy_start_stack =  NULL;
+    yyg->yy_start_stack = (int *) 0;
 
 /* Defined in main.c */
 #ifdef YY_STDINIT
@@ -3015,6 +2967,33 @@
     return 0;
 }
 
+/* User-visible API */
+
+/* ast_yylex_init is special because it creates the scanner itself, so it is
+ * the ONLY reentrant function that doesn't take the scanner as the last argument.
+ * That's why we explicitly handle the declaration, instead of using our macros.
+ */
+
+int ast_yylex_init(yyscan_t* ptr_yy_globals)
+
+{
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+
+    *ptr_yy_globals = (yyscan_t) ast_yyalloc ( sizeof( struct yyguts_t ), NULL );
+
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+
+    memset(*ptr_yy_globals,0,sizeof(struct yyguts_t));
+
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
 /* ast_yylex_destroy is for both reentrant and non-reentrant scanners. */
 int ast_yylex_destroy  (yyscan_t yyscanner)
 {
@@ -3035,13 +3014,8 @@
         ast_yyfree(yyg->yy_start_stack ,yyscanner );
         yyg->yy_start_stack = NULL;
 
-    /* Reset the globals. This is important in a non-reentrant scanner so the next time
-     * ast_yylex() is called, initialization will occur. */
-    yy_init_globals( yyscanner);
-
     /* Destroy the main struct (reentrant only). */
     ast_yyfree ( yyscanner , yyscanner );
-    yyscanner = NULL;
     return 0;
 }
 
@@ -3053,6 +3027,7 @@
 static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner)
 {
 	register int i;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
 	for ( i = 0; i < n; ++i )
 		s1[i] = s2[i];
 }
@@ -3062,6 +3037,7 @@
 static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner)
 {
 	register int n;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
 	for ( n = 0; s[n]; ++n )
 		;
 
@@ -3093,6 +3069,18 @@
 

[... 1730 lines stripped ...]


More information about the svn-commits mailing list