[Freeswitch-svn] [commit] r1696 - in freeswitch/branches/mishehu/src: . mod/codecs/mod_ilbc mod/codecs/mod_l16 mod/loggers/mod_cdr

Freeswitch SVN mishehu at freeswitch.org
Wed Jun 28 17:46:05 EDT 2006


Author: mishehu
Date: Wed Jun 28 17:46:04 2006
New Revision: 1696

Added:
   freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/csvcdr.cpp
   freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/csvcdr.h
Removed:
   freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/chanvars_parser.cpp
   freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/chanvars_parser.h
Modified:
   freeswitch/branches/mishehu/src/mod/codecs/mod_ilbc/mod_ilbc.c
   freeswitch/branches/mishehu/src/mod/codecs/mod_l16/mod_l16.c
   freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/anthmcdr.cpp
   freeswitch/branches/mishehu/src/switch_core.c
   freeswitch/branches/mishehu/src/switch_xml.c

Log:
Added in CsvCDR standard CSV output using a fixed format output for everything except chanvars.  Mostly tested.  Merged to trunk r1695.

Modified: freeswitch/branches/mishehu/src/mod/codecs/mod_ilbc/mod_ilbc.c
==============================================================================
--- freeswitch/branches/mishehu/src/mod/codecs/mod_ilbc/mod_ilbc.c	(original)
+++ freeswitch/branches/mishehu/src/mod/codecs/mod_ilbc/mod_ilbc.c	Wed Jun 28 17:46:04 2006
@@ -225,7 +225,7 @@
 
 
 static const switch_codec_implementation_t ilbc_102_8k_30ms_implementation = { 
-		/*.ianacode */ 97, 
+		/*.ianacode */ 102, 
 		/*.iananame */ "iLBC", 
 		/*.samples_per_second */ 8000, 
 		/*.bits_per_second */ NO_OF_BYTES_30MS*8*8000/BLOCKL_30MS,

Modified: freeswitch/branches/mishehu/src/mod/codecs/mod_l16/mod_l16.c
==============================================================================
--- freeswitch/branches/mishehu/src/mod/codecs/mod_l16/mod_l16.c	(original)
+++ freeswitch/branches/mishehu/src/mod/codecs/mod_l16/mod_l16.c	Wed Jun 28 17:46:04 2006
@@ -57,7 +57,6 @@
 									   void *encoded_data,
 									   uint32_t *encoded_data_len, uint32_t *encoded_rate, unsigned int *flag)
 {
-
 	/* NOOP indicates that the audio in is already the same as the audio out, so no conversion was necessary. */
 	if (codec && other_codec && codec->implementation->samples_per_second != other_codec->implementation->samples_per_second) {
 		memcpy(encoded_data, decoded_data, decoded_data_len);

Modified: freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/anthmcdr.cpp
==============================================================================
--- freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/anthmcdr.cpp	(original)
+++ freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/anthmcdr.cpp	Wed Jun 28 17:46:04 2006
@@ -43,8 +43,8 @@
 	
 		outputfile.open(outputfile_name.c_str());
 	
-		//bool fixed = 0;
-		process_channel_variables(chanvars_supp_list,chanvars_fixed_list,newchannel->channel);
+		bool repeat = 1;
+		process_channel_variables(chanvars_supp_list,chanvars_fixed_list,newchannel->channel,repeat);
 	}
 }
 
@@ -96,7 +96,7 @@
 			}
 			else if (!strcmp(var, "chanvars_fixed"))
 			{
-				switch_console_printf(SWITCH_CHANNEL_LOG,"AnthmCDR has no need for a fixed or supplemental list of channel variables due to the nature of the format.  Please use the setting parameter of \"chanvars\" or \"chanvars_supp\" instead and try again.\n");
+				switch_console_printf(SWITCH_CHANNEL_LOG,"AnthmCDR has no need for a fixed or supplemental list of channel variables due to the nature of the format.  Please use the setting parameter of \"chanvars\" instead and try again.\n");
 			}
 			else if (!strcmp(var, "chanvars_supp"))
 			{
@@ -147,8 +147,8 @@
 		outputfile << "\t\'chanvars\' => {" << std::endl;
 		if(chanvars_supp.size() > 0 )
 		{
-			std::list<std::pair<std::string,std::string> >::iterator iItr,iEnd;
-			for(iItr = chanvars_fixed.begin(), iEnd = chanvars_fixed.end() ; iItr != iEnd; iItr++)
+			std::map<std::string,std::string>::iterator iItr,iEnd;
+			for(iItr = chanvars_supp.begin(), iEnd = chanvars_supp.end() ; iItr != iEnd; iItr++)
 				outputfile << "\t\t\'" << iItr->first << "\' = \'" << iItr->second << "\'," << std::endl;
 		}
 		outputfile << "\t}," << std::endl << "};" << std::endl << std::endl;

Added: freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/csvcdr.cpp
==============================================================================
--- (empty file)
+++ freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/csvcdr.cpp	Wed Jun 28 17:46:04 2006
@@ -0,0 +1,249 @@
+#include <switch.h>
+#include "csvcdr.h"
+
+CsvCDR::CsvCDR() : BaseCDR()
+{
+	memset(formattedcallstartdate,0,100);
+	memset(formattedcallanswerdate,0,100);
+	memset(formattedcallenddate,0,100);
+}
+
+CsvCDR::CsvCDR(switch_mod_cdr_newchannel_t *newchannel) : BaseCDR(newchannel)
+{
+	memset(formattedcallstartdate,0,100);
+	memset(formattedcallanswerdate,0,100);
+	memset(formattedcallenddate,0,100);
+	
+	if(newchannel != 0)
+	{
+		switch_time_exp_t tempcallstart, tempcallanswer, tempcallend;
+		memset(&tempcallstart,0,sizeof(tempcallstart));
+		memset(&tempcallanswer,0,sizeof(tempcallanswer));
+		memset(&tempcallend,0,sizeof(tempcallend));
+		switch_time_exp_lt(&tempcallstart, callstartdate);
+		switch_time_exp_lt(&tempcallanswer, callanswerdate);
+		switch_time_exp_lt(&tempcallend, callenddate);
+		
+		// Format the times
+		apr_size_t retsizecsd, retsizecad, retsizeced;  //csd == callstartdate, cad == callanswerdate, ced == callenddate, ceff == callenddate_forfile
+		char format[] = "%F %T";
+		switch_strftime(formattedcallstartdate,&retsizecsd,sizeof(formattedcallstartdate),format,&tempcallstart);
+		switch_strftime(formattedcallanswerdate,&retsizecad,sizeof(formattedcallanswerdate),format,&tempcallanswer);
+		switch_strftime(formattedcallenddate,&retsizeced,sizeof(formattedcallenddate),format,&tempcallend);
+
+		process_channel_variables(chanvars_fixed_list,newchannel->channel);
+		process_channel_variables(chanvars_supp_list,chanvars_fixed_list,newchannel->channel,repeat_fixed_in_supp);
+	}
+}
+
+CsvCDR::~CsvCDR()
+{
+
+}
+
+bool CsvCDR::activated=0;
+bool CsvCDR::logchanvars=0;
+bool CsvCDR::connectionstate=0;
+bool CsvCDR::repeat_fixed_in_supp=0;
+std::string CsvCDR::outputfile_path;
+std::ofstream CsvCDR::outputfile;
+std::ofstream::pos_type CsvCDR::filesize_limit = (10 * 1024 * 1024); // Default file size is 10MB
+std::list<std::string> CsvCDR::chanvars_fixed_list;
+std::list<std::string> CsvCDR::chanvars_supp_list;
+
+void CsvCDR::connect(switch_xml_t& cfg, switch_xml_t& xml, switch_xml_t& settings, switch_xml_t& param)
+{
+	switch_console_printf(SWITCH_CHANNEL_LOG, "CsvCDR::connect() - Loading configuration file.\n");
+	activated = 0; // Set it as inactive initially
+	connectionstate = 0; // Initialize it to false to show that we aren't yet connected.
+	
+	if ((settings = switch_xml_child(cfg, "csvcdr"))) 
+	{
+		int count_config_params = 0;  // Need to make sure all params are set before we load
+		for (param = switch_xml_child(settings, "param"); param; param = param->next) 
+		{
+			char *var = (char *) switch_xml_attr_soft(param, "name");
+			char *val = (char *) switch_xml_attr_soft(param, "value");
+
+			if (!strcmp(var, "path"))
+			{
+				if(val != 0)
+					outputfile_path = val;
+				count_config_params++;
+			}
+			else if (!strcmp(var, "chanvars_supp")) 
+			{
+				if(val != 0)
+				{
+					std::string unparsed;
+					unparsed = val;
+					if(unparsed.size() > 0)
+					{
+						bool fixed = 0;
+						parse_channel_variables_xconfig(unparsed,chanvars_supp_list,fixed);
+						logchanvars=1;
+					}
+				}
+			}
+			else if (!strcmp(var, "chanvars_fixed"))
+			{
+				if(val != 0)
+				{
+					std::string unparsed;
+					unparsed = val;
+					if(unparsed.size() > 0)
+					{
+						bool fixed = 1;
+						parse_channel_variables_xconfig(unparsed,chanvars_fixed_list,fixed);
+						logchanvars=1;
+					}
+				}
+			}
+			else if (!strcmp(var, "repeat_fixed_in_supp"))
+			{
+				if(!strcmp(val,"1"))
+					repeat_fixed_in_supp = val;
+				else if (!strcmp(val,"y") || !strcmp(val,"y"))
+					repeat_fixed_in_supp = 0;
+			}
+			else if (!strcmp(var, "size_limit"))
+			{
+				if(val != 0)
+				{
+					filesize_limit = atoi(val) * 1024 * 1024; // Value is in MB
+					std::cout << "File size limit from config file is " << filesize_limit << " byte(s)." << std::endl;
+				}
+			}
+		}
+		
+		if(count_config_params > 0)
+		{
+			open_file();
+			if(outputfile.good())
+			{
+				activated = 1;
+				switch_console_printf(SWITCH_CHANNEL_LOG,"CsvCDR activated, log rotation will occur at or after %d MB",(filesize_limit/1024/1024));
+			}
+		}
+		else
+			switch_console_printf(SWITCH_CHANNEL_LOG,"CsvCDR::connect(): You did not specify the minimum parameters for using this module.  You must specify at least a path to have the records logged to.\n");
+	}
+}
+
+void CsvCDR::check_file_size_and_open()
+{
+	// Test if the file has been opened or not
+	if(outputfile)
+	{
+		if(outputfile.tellp() > filesize_limit)
+			outputfile.close();
+	}
+	
+	if (!outputfile)
+	{
+		open_file();
+	}
+}
+
+void CsvCDR::open_file()
+{
+	switch_time_t now = switch_time_now();
+	switch_time_exp_t now_converted;
+	memset(&now_converted,0,sizeof(now_converted));
+		
+	switch_time_exp_lt(&now_converted,now);
+		
+	apr_size_t retsize;		
+	char format[] = "%F %T";
+	char formatteddate[100];
+	memset(formatteddate,0,100);
+	switch_strftime(formatteddate,&retsize,100,format,&now_converted);
+		
+	std::string filename = outputfile_path;
+	filename.append("/");
+	filename.append(formatteddate);
+	filename.append(".csv");
+	outputfile.clear();
+		
+	outputfile.open(filename.c_str(),std::ios_base::app|std::ios_base::binary);
+	if(outputfile.fail())
+	{
+		switch_console_printf(SWITCH_CHANNEL_LOG,"Could not open the CSV file %s .  CsvCDR logger will not be functional until this is resolved and a reload is issued.  Failbit is set to %d.\n",filename.c_str(),outputfile.fail());
+		activated = 0;
+	}
+	
+}
+
+bool CsvCDR::process_record()
+{
+	check_file_size_and_open();
+	bool retval = 0;
+	if(active)
+	{
+		// Format the call record and proceed from here...
+		outputfile << "\"" << callstartdate << "\",\"";
+		outputfile << callanswerdate << "\",\"";
+		outputfile << callenddate << "\",\"";
+		outputfile << hangupcause_text << "\",\"";
+		outputfile << hangupcause << "\",\"";
+		outputfile << clid << "\",\"";
+		outputfile << originated << "\",\"";
+		outputfile << dialplan << "\",\"";
+		outputfile << myuuid << "\",\"";
+		outputfile << destuuid << "\",\"";
+		outputfile << src << "\",\"";
+		outputfile << dst << "\",\"";
+		outputfile << srcchannel << "\",\"";
+		outputfile << dstchannel << "\",\"";
+		outputfile << ani << "\",\"";
+		outputfile << ani2 << "\",\"";
+		outputfile << network_addr << "\",\"";
+		outputfile << lastapp << "\",\"";
+		outputfile << lastdata << "\",\"";
+		outputfile << billusec << "\",\"";
+		outputfile << disposition << "\",\"";
+		outputfile << amaflags << "\"";
+		
+		// Now to process chanvars, fixed ones first
+		if(chanvars_fixed.size() > 0 )
+		{
+			std::list<std::pair<std::string,std::string> >::iterator iItr, iEnd;
+			for(iItr = chanvars_fixed.begin(), iEnd = chanvars_fixed.end(); iItr != iEnd; iItr++)
+				outputfile << ",\"" << iItr->second << "\"";
+		}
+		
+		if(chanvars_supp.size() > 0 )
+		{
+			std::map<std::string,std::string>::iterator iItr,iEnd;
+			for(iItr = chanvars_supp.begin(), iEnd = chanvars_supp.end() ; iItr != iEnd; iItr++)
+				outputfile << ",\"" << iItr->first << "=" << iItr->second << "\"";
+		}
+		outputfile << std::endl;
+		retval = 1;
+	}
+	
+	return retval;
+}
+
+bool CsvCDR::is_activated()
+{
+	return activated;
+}
+
+void CsvCDR::tempdump_record()
+{
+
+}
+
+void CsvCDR::reread_tempdumped_records()
+{
+
+}
+
+void CsvCDR::disconnect()
+{
+	outputfile.close();
+	switch_console_printf(SWITCH_CHANNEL_LOG,"Shutting down CsvCDR...  Done!");	
+}
+
+AUTO_REGISTER_BASECDR(CsvCDR);

Added: freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/csvcdr.h
==============================================================================
--- (empty file)
+++ freeswitch/branches/mishehu/src/mod/loggers/mod_cdr/csvcdr.h	Wed Jun 28 17:46:04 2006
@@ -0,0 +1,43 @@
+#include "baseregistry.h"
+#include <switch.h>
+// #include <sys/types.h>
+// #include <sys/stat.h>
+// #include <fcntl.h>
+#include <iostream>
+#include <fstream>
+#include <list>
+
+#ifndef CSVCDR
+#define CSVCDR
+
+class CsvCDR : public BaseCDR {
+	public:
+		CsvCDR();
+		CsvCDR(switch_mod_cdr_newchannel_t *newchannel);
+		//CsvCDR(const CsvCDR& copyFrom);
+		virtual ~CsvCDR();
+		virtual bool process_record();
+		virtual void connect(switch_xml_t& cfg, switch_xml_t& xml, switch_xml_t& settings, switch_xml_t& param); // connect and disconnect need to be static because we're persisting connections until shutdown
+		virtual void disconnect();
+		virtual bool is_activated();
+		virtual void tempdump_record();
+		virtual void reread_tempdumped_records();
+
+	private:
+		static bool activated; // Is this module activated?
+		static bool connectionstate; // What is the status of the connection?
+		static bool logchanvars;
+		static bool repeat_fixed_in_supp; // Repeat the fixed chanvars in the supplemental?
+		static std::string outputfile_path; // The directory we'll dump these into
+		static std::list<std::string> chanvars_fixed_list; // Normally this would be used, but not in this class
+		static std::list<std::string> chanvars_supp_list; // This will hold the list for all chanvars here
+		char formattedcallstartdate[100];
+		char formattedcallanswerdate[100];
+		char formattedcallenddate[100];
+		static std::ofstream outputfile;
+		static std::ofstream::pos_type filesize_limit;
+		void check_file_size_and_open(); // Checks the size of the file, and if it's greater than size allowed, rotates it.
+		void open_file();
+};
+
+#endif

Modified: freeswitch/branches/mishehu/src/switch_core.c
==============================================================================
--- freeswitch/branches/mishehu/src/switch_core.c	(original)
+++ freeswitch/branches/mishehu/src/switch_core.c	Wed Jun 28 17:46:04 2006
@@ -1057,9 +1057,13 @@
 															 int timeout, int stream_id)
 {
 	switch_io_event_hook_read_frame_t *ptr;
-	switch_status_t status = SWITCH_STATUS_FALSE;
-	int need_codec = 0, perfect = 0;
-
+	switch_status_t status;
+	int need_codec, perfect;
+ top:
+	
+	status = SWITCH_STATUS_FALSE;
+	need_codec = perfect = 0;
+	
 	assert(session != NULL);
 	*frame = NULL;
 
@@ -1180,18 +1184,22 @@
 					session->raw_read_frame.rate = (*frame)->rate;
 				} else {
 					session->raw_read_frame.datalen = (uint32_t)switch_buffer_read(session->raw_read_buffer,
-																		 session->raw_read_frame.data,
-																		 session->read_codec->implementation->
-																		 bytes_per_frame);
+																				   session->raw_read_frame.data,
+																				   session->read_codec->implementation->bytes_per_frame);
+					
 					session->raw_read_frame.rate = session->read_codec->implementation->samples_per_second;
 					enc_frame = &session->raw_read_frame;
 				}
 				session->enc_read_frame.datalen = session->enc_read_frame.buflen;
+				assert(session->read_codec != NULL);				
+				assert(enc_frame != NULL);
+				assert(enc_frame->data != NULL);
+				
 				status = switch_core_codec_encode(session->read_codec,
 												  enc_frame->codec,
 												  enc_frame->data,
 												  enc_frame->datalen,
-												  enc_frame->codec->implementation->samples_per_second,
+												  session->read_codec->implementation->samples_per_second,
 												  session->enc_read_frame.data,
 												  &session->enc_read_frame.datalen,
 												  &session->enc_read_frame.rate, 
@@ -1215,6 +1223,8 @@
 					status = SWITCH_STATUS_GENERR;
 					break;
 				}
+			} else {
+				goto top;
 			}
 		}
 	}

Modified: freeswitch/branches/mishehu/src/switch_xml.c
==============================================================================
--- freeswitch/branches/mishehu/src/switch_xml.c	(original)
+++ freeswitch/branches/mishehu/src/switch_xml.c	Wed Jun 28 17:46:04 2006
@@ -1001,26 +1001,32 @@
     return *dst;
 }
 
+#define XML_INDENT "  "
 // Recursively converts each tag to xml appending it to *s. Reallocates *s if
 // its length excedes max. start is the location of the previous tag in the
 // parent tag's character content. Returns *s.
 static char *switch_xml_toxml_r(switch_xml_t xml, char **s, switch_size_t *len, switch_size_t *max,
-                    switch_size_t start, char ***attr)
+                    switch_size_t start, char ***attr, uint32_t *count)
 {
     int i, j;
     char *txt = (xml->parent) ? xml->parent->txt : "";
     switch_size_t off = 0;
+	uint32_t lcount = 0;
 
     // parent character content up to this tag
     *s = switch_xml_ampencode(txt + start, xml->off - start, s, len, max, 0);
 
-    while (*len + strlen(xml->name) + 4 > *max) // reallocate s
+    while (*len + strlen(xml->name) + 5 + (strlen(XML_INDENT) * (*count)) > *max) // reallocate s
         *s = realloc(*s, *max += SWITCH_XML_BUFSIZE);
 
+	for (lcount = 0; lcount < *count; lcount++) {
+		*len += sprintf(*s + *len, "%s", XML_INDENT); // indent
+	}
+	
     *len += sprintf(*s + *len, "<%s", xml->name); // open tag
     for (i = 0; xml->attr[i]; i += 2) { // tag attributes
         if (switch_xml_attr(xml, xml->attr[i]) != xml->attr[i + 1]) continue;
-        while (*len + strlen(xml->attr[i]) + 7 > *max) // reallocate s
+        while (*len + strlen(xml->attr[i]) + 7 + (strlen(XML_INDENT) * (*count)) > *max) // reallocate s
             *s = realloc(*s, *max += SWITCH_XML_BUFSIZE);
 
         *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
@@ -1032,26 +1038,43 @@
     for (j = 1; attr[i] && attr[i][j]; j += 3) { // default attributes
         if (! attr[i][j + 1] || switch_xml_attr(xml, attr[i][j]) != attr[i][j + 1])
             continue; // skip duplicates and non-values
-        while (*len + strlen(attr[i][j]) + 7 > *max) // reallocate s
+        while (*len + strlen(attr[i][j]) + 8 + (strlen(XML_INDENT) * (*count)) > *max) // reallocate s
             *s = realloc(*s, *max += SWITCH_XML_BUFSIZE);
 
         *len += sprintf(*s + *len, " %s=\"", attr[i][j]);
         switch_xml_ampencode(attr[i][j + 1], 0, s, len, max, 1);
         *len += sprintf(*s + *len, "\"");
     }
-    *len += sprintf(*s + *len, ">");
+    *len += sprintf(*s + *len, xml->child ? ">\n" : "/>\n");
 
-    *s = (xml->child) ? switch_xml_toxml_r(xml->child, s, len, max, 0, attr) //child
-                      : switch_xml_ampencode(xml->txt, 0, s, len, max, 0);  //data
-    
-    while (*len + strlen(xml->name) + 4 > *max) // reallocate s
+	if (xml->child) {
+		(*count)++;
+		*s = switch_xml_toxml_r(xml->child, s, len, max, 0, attr, count);
+	} else {
+		*s = switch_xml_ampencode(xml->txt, 0, s, len, max, 0);  //data
+	}
+	
+    while (*len + strlen(xml->name) + 5 + (strlen(XML_INDENT) * (*count)) > *max) // reallocate s
         *s = realloc(*s, *max += SWITCH_XML_BUFSIZE);
 
-    *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
 
+	if (xml->child) {
+		for (lcount = 0; lcount < *count; lcount++) {
+			*len += sprintf(*s + *len, "%s", XML_INDENT); // indent
+		}
+		*len += sprintf(*s + (*len), "</%s>\n", xml->name); // close tag
+	}
+
     while (txt[off] && off < xml->off) off++; // make sure off is within bounds
-    return (xml->ordered) ? switch_xml_toxml_r(xml->ordered, s, len, max, off, attr)
-                          : switch_xml_ampencode(txt + off, 0, s, len, max, 0);
+
+	if (xml->ordered) {
+		return switch_xml_toxml_r(xml->ordered, s, len, max, off, attr, count);
+
+	} else {
+		if (*count > 0)
+			(*count)--;
+		return switch_xml_ampencode(txt + off, 0, s, len, max, 0);
+	}
 }
 
 // converts an switch_xml structure back to xml, returning it as a string that must
@@ -1063,6 +1086,7 @@
     switch_size_t len = 0, max = SWITCH_XML_BUFSIZE;
     char *s = strcpy(malloc(max), ""), *t, *n;
     int i, j, k;
+	uint32_t count = 0;
 
     if (! xml || ! xml->name) return realloc(s, len + 1);
     while (root->xml.parent) root = (switch_xml_root_t)root->xml.parent; // root tag
@@ -1073,12 +1097,12 @@
             if (root->pi[i][k][j - 1] == '>') continue; // not pre-root
             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
                 s = realloc(s, max += SWITCH_XML_BUFSIZE);
-            len += sprintf(s + len, "<?%s%s%s?>\n", t, *n ? " " : "", n);
+            len += sprintf(s + len, "<?%s%s%s?>", t, *n ? " " : "", n);
         }
     }
 
     xml->parent = xml->ordered = NULL;
-    s = switch_xml_toxml_r(xml, &s, &len, &max, 0, root->attr);
+    s = switch_xml_toxml_r(xml, &s, &len, &max, 0, root->attr, &count);
     xml->parent = p;
     xml->ordered = o;
 



More information about the Freeswitch-svn mailing list