From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:02:20 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:02:20 +0100
Subject: [Box Backup-dev] COMMIT r902 - box/chris/merge/lib/server
Message-ID: <E1GIvYC-000KkA-V6@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:02:20 +0100 (Fri, 01 Sep 2006)
New Revision: 902

Modified:
   box/chris/merge/lib/server/ServerStream.h
Log:
(refs #3)

Added an OnIdle method which can be overridden by subclasses for idle
tasks. Used for housekeeping on Win32.

Avoid forking on Win32, and trying to clean up after children.


Modified: box/chris/merge/lib/server/ServerStream.h
===================================================================
--- box/chris/merge/lib/server/ServerStream.h	2006-08-31 22:58:30 UTC (rev 901)
+++ box/chris/merge/lib/server/ServerStream.h	2006-08-31 23:02:20 UTC (rev 902)
@@ -56,6 +56,8 @@
 		return "generic-stream-server";
 	}
 
+	virtual void OnIdle() { }
+
 	virtual void Run()
 	{
 		// Set process title as appropraite
@@ -215,7 +217,7 @@
 					if(connection.get())
 					{
 						// Since this is a template parameter, the if() will be optimised out by the compiler
-						if(ForkToHandleRequests)
+						if(WillForkToHandleRequests())
 						{
 							pid_t pid = ::fork();
 							switch(pid)
@@ -262,9 +264,11 @@
 						}
 					}
 				}
-				
+
+				OnIdle();
+
 				// Clean up child processes (if forking daemon)
-				if(ForkToHandleRequests)
+				if(WillForkToHandleRequests())
 				{
 					int status = 0;
 					int p = 0;
@@ -301,7 +305,11 @@
 	// depends on the forking model in case someone changes it later.
 	bool WillForkToHandleRequests()
 	{
+		#ifdef WIN32
+		return false;
+		#else
 		return ForkToHandleRequests;
+		#endif // WIN32
 	}
 
 private:


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:02:21 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:02:21 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.dfd10d9e66ca39fb52b741c6995318b2@fluffy.co.uk>

--===============1395839632==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


aW5lIGJhY2t1cCBzeXN0ZW0gZm9yIFVOSVgu

--===============1395839632==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:06:54 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:06:54 -0000
Subject: [Box Backup-dev] =?utf-8?q?=235=3A_Debian_start/stop_scripts?=
Message-ID: <072.6898ee49dc8dd3ae76f3708a92e4fa59@fluffy.co.uk>

--===============0346439327==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


VU5JWC4=

--===============0346439327==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:09:44 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:09:44 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.7ca738850aa793b7519b92a431470feb@fluffy.co.uk>

--===============1426306769==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


dG9tYXRpYyBvbi1saW5lIGJhY2t1cCBzeXN0ZW0gZm9yIFVOSVgu

--===============1426306769==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:12:51 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:12:51 +0100
Subject: [Box Backup-dev] COMMIT r903 - box/chris/merge/lib/server
Message-ID: <E1GIviN-000FTx-DG@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:12:51 +0100 (Fri, 01 Sep 2006)
New Revision: 903

Modified:
   box/chris/merge/lib/server/SocketStream.h
Log:
Revert to trunk


Modified: box/chris/merge/lib/server/SocketStream.h
===================================================================
--- box/chris/merge/lib/server/SocketStream.h	2006-08-31 23:02:20 UTC (rev 902)
+++ box/chris/merge/lib/server/SocketStream.h	2006-08-31 23:12:51 UTC (rev 903)
@@ -14,10 +14,8 @@
 
 #ifdef WIN32
 	typedef SOCKET tOSSocketHandle;
-	#define INVALID_SOCKET_VALUE (tOSSocketHandle)(-1)
 #else
 	typedef int tOSSocketHandle;
-	#define INVALID_SOCKET_VALUE -1
 #endif
 
 // --------------------------------------------------------------------------


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:22:55 2006
From: boxbackup-dev@fluffy.co.uk (Chris Wilson)
Date: Fri, 1 Sep 2006 00:22:55 +0100 (BST)
Subject: [Box Backup-dev] changeset [819]
In-Reply-To: <1157065167.26754.16.camel@avenin.ebourne.me.uk>
References: <1156975704.2413.1.camel@avenin.ebourne.me.uk>
 <Pine.LNX.4.62.0608310904090.28816@top.qwarx.com>
 <20060831102459.fvnuefdw0848gscc@ebourne.me.uk>
 <1D3961A7-0885-41CC-9F73-273CE2BB3AFA@fluffy.co.uk>
 <Pine.LNX.4.64.0608311319030.11479@localhost>  <20060831134917.d3rpxireaskksgwc@ebourne.me.uk>
 <Pine.LNX.4.62.0608312043490.28816@top.qwarx.com>
 <1157065167.26754.16.camel@avenin.ebourne.me.uk>
Message-ID: <Pine.LNX.4.62.0609010017450.28816@top.qwarx.com>

Hi Martin,

On Thu, 31 Aug 2006, Martin Ebourne wrote:

> On Thu, 2006-08-31 at 20:51 +0100, Chris Wilson wrote:
>> There is a possible clean solution, which is to use the Windows flag
>> FILE_FLAG_DELETE_ON_CLOSE when opening the file. To avoid the #ifdef, I
>> think I would have to invent a new flag to pass to the FileStream
>> constructor, which does nothing (define to zero) on other platforms, but
>> activates this Windows flag in openfile() on Win32.
>>
>> Does this seem like a sensible solution? And what should that flag be
>> called? O_DELETE_ON_CLOSE_WIN32?
>
> How about FileStream handling this platform specific stuff. All you need
> to do is tell FileStream when you construct it (use an overloaded
> constructor or give an extra bool parameter to the constructor) that you
> want the file automatically deleted.

Seems like a good idea, much cleaner. Will implement tomorrow.

> On windows it would then pass FILE_FLAG_DELETE_ON_CLOSE and on unix it
> would unlink it after opening it.

FileStream calls ::openfile (from lib/win32/emu.cpp) to open the file, so 
I would have to pass a new flag anyway, or a new parameter, to make this 
happen. Alternatibely, FileStream could delete the file itself in the 
Close method.

> Does that cover all possibilities? If so it would certainly tidy up a 
> lot of code around the place that is closing and deleting files.

I'm not certain that it covers them all, particularly renaming over open 
files, but I'm sure it would help.

Cheers, Chris.
-- 
_ ___ __     _
  / __/ / ,__(_)_  | Chris Wilson <0000 at qwirx.com> - Cambs UK |
/ (_/ ,\/ _/ /_ \ | Security/C/C++/Java/Perl/SQL/HTML Developer |
\ _/_/_/_//_/___/ | We are GNU-free your mind-and your software |


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:30:04 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:30:04 +0100
Subject: [Box Backup-dev] COMMIT r904 - box/chris/merge/lib/server
Message-ID: <E1GIvz2-0001C6-7f@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:30:04 +0100 (Fri, 01 Sep 2006)
New Revision: 904

Modified:
   box/chris/merge/lib/server/SocketStream.h
Log:
(refs #3)

Define a suitable constant for invalid socket handles, for use in 
SocketStream.cpp


Modified: box/chris/merge/lib/server/SocketStream.h
===================================================================
--- box/chris/merge/lib/server/SocketStream.h	2006-08-31 23:12:51 UTC (rev 903)
+++ box/chris/merge/lib/server/SocketStream.h	2006-08-31 23:30:04 UTC (rev 904)
@@ -14,8 +14,10 @@
 
 #ifdef WIN32
 	typedef SOCKET tOSSocketHandle;
+	#define INVALID_SOCKET_VALUE INVALID_SOCKET
 #else
 	typedef int tOSSocketHandle;
+	#define INVALID_SOCKET_VALUE -1
 #endif
 
 // --------------------------------------------------------------------------


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:30:05 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:30:05 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.eeb5638668241de061aa20a3f3798177@fluffy.co.uk>

--===============0958727334==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============0958727334==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:30:42 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:30:42 +0100
Subject: [Box Backup-dev] COMMIT r905 - box/chris/merge/lib/server
Message-ID: <E1GIvze-000BDD-Dg@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:30:42 +0100 (Fri, 01 Sep 2006)
New Revision: 905

Modified:
   box/chris/merge/lib/server/SocketStream.cpp
Log:
Revert to trunk


Modified: box/chris/merge/lib/server/SocketStream.cpp
===================================================================
--- box/chris/merge/lib/server/SocketStream.cpp	2006-08-31 23:30:04 UTC (rev 904)
+++ box/chris/merge/lib/server/SocketStream.cpp	2006-08-31 23:30:42 UTC (rev 905)
@@ -36,7 +36,7 @@
 //
 // --------------------------------------------------------------------------
 SocketStream::SocketStream()
-	: mSocketHandle(INVALID_SOCKET_VALUE),
+	: mSocketHandle(-1),
 	  mReadClosed(false),
 	  mWriteClosed(false),
 	  mBytesRead(0),
@@ -85,7 +85,7 @@
 	{
 		THROW_EXCEPTION(ServerException, BadSocketHandle);
 	}
-	if(mSocketHandle == INVALID_SOCKET_VALUE)
+	if(mSocketHandle == -1)
 	{
 		THROW_EXCEPTION(ServerException, DupError);
 	}
@@ -101,7 +101,7 @@
 // --------------------------------------------------------------------------
 SocketStream::~SocketStream()
 {
-	if(mSocketHandle != INVALID_SOCKET_VALUE)
+	if(mSocketHandle != -1)
 	{
 		Close();
 	}
@@ -117,10 +117,7 @@
 // --------------------------------------------------------------------------
 void SocketStream::Attach(int socket)
 {
-	if(mSocketHandle != INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, SocketAlreadyOpen)
-	}
+	if(mSocketHandle != -1) {THROW_EXCEPTION(ServerException, SocketAlreadyOpen)}
 
 	mSocketHandle = socket;
 	ResetCounters();
@@ -137,10 +134,7 @@
 // --------------------------------------------------------------------------
 void SocketStream::Open(int Type, const char *Name, int Port)
 {
-	if(mSocketHandle != INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, SocketAlreadyOpen)
-	}
+	if(mSocketHandle != -1) {THROW_EXCEPTION(ServerException, SocketAlreadyOpen)}
 	
 	// Setup parameters based on type, looking up names if required
 	int sockDomain = 0;
@@ -150,7 +144,7 @@
 
 	// Create the socket
 	mSocketHandle = ::socket(sockDomain, SOCK_STREAM, 0 /* let OS choose protocol */);
-	if(mSocketHandle == INVALID_SOCKET_VALUE)
+	if(mSocketHandle == -1)
 	{
 		THROW_EXCEPTION(ServerException, SocketOpenError)
 	}
@@ -164,7 +158,7 @@
 #else
 		::close(mSocketHandle);
 #endif
-		mSocketHandle = INVALID_SOCKET_VALUE;
+		mSocketHandle = -1;
 		THROW_EXCEPTION(ConnectionException, Conn_SocketConnectError)
 	}
 	ResetCounters();
@@ -180,10 +174,7 @@
 // --------------------------------------------------------------------------
 int SocketStream::Read(void *pBuffer, int NBytes, int Timeout)
 {
-	if(mSocketHandle == INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, BadSocketHandle)
-	}
+	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
 
 	if(Timeout != IOStream::TimeOutInfinite)
 	{
@@ -256,10 +247,7 @@
 // --------------------------------------------------------------------------
 void SocketStream::Write(const void *pBuffer, int NBytes)
 {
-	if(mSocketHandle == INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, BadSocketHandle)
-	}
+	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
 	
 	// Buffer in byte sized type.
 	ASSERT(sizeof(char) == 1);
@@ -323,10 +311,7 @@
 // --------------------------------------------------------------------------
 void SocketStream::Close()
 {
-	if(mSocketHandle == INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, BadSocketHandle)
-	}
+	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
 #ifdef WIN32
 	if(::closesocket(mSocketHandle) == -1)
 #else
@@ -335,7 +320,7 @@
 	{
 		THROW_EXCEPTION(ServerException, SocketCloseError)
 	}
-	mSocketHandle = INVALID_SOCKET_VALUE;
+	mSocketHandle = -1;
 }
 
 // --------------------------------------------------------------------------
@@ -348,10 +333,7 @@
 // --------------------------------------------------------------------------
 void SocketStream::Shutdown(bool Read, bool Write)
 {
-	if(mSocketHandle == INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, BadSocketHandle)
-	}
+	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
 	
 	// Do anything?
 	if(!Read && !Write) return;
@@ -406,10 +388,7 @@
 // --------------------------------------------------------------------------
 tOSSocketHandle SocketStream::GetSocketHandle()
 {
-	if(mSocketHandle == INVALID_SOCKET_VALUE) 
-	{
-		THROW_EXCEPTION(ServerException, BadSocketHandle)
-	}
+	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
 	return mSocketHandle;
 }
 


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:31:38 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:31:38 +0100
Subject: [Box Backup-dev] COMMIT r906 - box/chris/merge/lib/server
Message-ID: <E1GIw0Y-000Mo8-4i@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:31:38 +0100 (Fri, 01 Sep 2006)
New Revision: 906

Modified:
   box/chris/merge/lib/server/SocketStream.cpp
Log:
Use INVALID_SOCKET_VALUE instead of -1


Modified: box/chris/merge/lib/server/SocketStream.cpp
===================================================================
--- box/chris/merge/lib/server/SocketStream.cpp	2006-08-31 23:30:42 UTC (rev 905)
+++ box/chris/merge/lib/server/SocketStream.cpp	2006-08-31 23:31:38 UTC (rev 906)
@@ -36,7 +36,7 @@
 //
 // --------------------------------------------------------------------------
 SocketStream::SocketStream()
-	: mSocketHandle(-1),
+	: mSocketHandle(INVALID_SOCKET_VALUE),
 	  mReadClosed(false),
 	  mWriteClosed(false),
 	  mBytesRead(0),
@@ -85,7 +85,7 @@
 	{
 		THROW_EXCEPTION(ServerException, BadSocketHandle);
 	}
-	if(mSocketHandle == -1)
+	if(mSocketHandle == INVALID_SOCKET_VALUE)
 	{
 		THROW_EXCEPTION(ServerException, DupError);
 	}
@@ -101,7 +101,7 @@
 // --------------------------------------------------------------------------
 SocketStream::~SocketStream()
 {
-	if(mSocketHandle != -1)
+	if(mSocketHandle != INVALID_SOCKET_VALUE)
 	{
 		Close();
 	}
@@ -117,7 +117,10 @@
 // --------------------------------------------------------------------------
 void SocketStream::Attach(int socket)
 {
-	if(mSocketHandle != -1) {THROW_EXCEPTION(ServerException, SocketAlreadyOpen)}
+	if(mSocketHandle != INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, SocketAlreadyOpen)
+	}
 
 	mSocketHandle = socket;
 	ResetCounters();
@@ -134,7 +137,10 @@
 // --------------------------------------------------------------------------
 void SocketStream::Open(int Type, const char *Name, int Port)
 {
-	if(mSocketHandle != -1) {THROW_EXCEPTION(ServerException, SocketAlreadyOpen)}
+	if(mSocketHandle != INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, SocketAlreadyOpen)
+	}
 	
 	// Setup parameters based on type, looking up names if required
 	int sockDomain = 0;
@@ -144,7 +150,7 @@
 
 	// Create the socket
 	mSocketHandle = ::socket(sockDomain, SOCK_STREAM, 0 /* let OS choose protocol */);
-	if(mSocketHandle == -1)
+	if(mSocketHandle == INVALID_SOCKET_VALUE)
 	{
 		THROW_EXCEPTION(ServerException, SocketOpenError)
 	}
@@ -158,7 +164,7 @@
 #else
 		::close(mSocketHandle);
 #endif
-		mSocketHandle = -1;
+		mSocketHandle = INVALID_SOCKET_VALUE;
 		THROW_EXCEPTION(ConnectionException, Conn_SocketConnectError)
 	}
 	ResetCounters();
@@ -174,7 +180,10 @@
 // --------------------------------------------------------------------------
 int SocketStream::Read(void *pBuffer, int NBytes, int Timeout)
 {
-	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
+	if(mSocketHandle == INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, BadSocketHandle)
+	}
 
 	if(Timeout != IOStream::TimeOutInfinite)
 	{
@@ -247,7 +256,10 @@
 // --------------------------------------------------------------------------
 void SocketStream::Write(const void *pBuffer, int NBytes)
 {
-	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
+	if(mSocketHandle == INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, BadSocketHandle)
+	}
 	
 	// Buffer in byte sized type.
 	ASSERT(sizeof(char) == 1);
@@ -311,7 +323,10 @@
 // --------------------------------------------------------------------------
 void SocketStream::Close()
 {
-	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
+	if(mSocketHandle == INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, BadSocketHandle)
+	}
 #ifdef WIN32
 	if(::closesocket(mSocketHandle) == -1)
 #else
@@ -320,7 +335,7 @@
 	{
 		THROW_EXCEPTION(ServerException, SocketCloseError)
 	}
-	mSocketHandle = -1;
+	mSocketHandle = INVALID_SOCKET_VALUE;
 }
 
 // --------------------------------------------------------------------------
@@ -333,7 +348,10 @@
 // --------------------------------------------------------------------------
 void SocketStream::Shutdown(bool Read, bool Write)
 {
-	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
+	if(mSocketHandle == INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, BadSocketHandle)
+	}
 	
 	// Do anything?
 	if(!Read && !Write) return;
@@ -388,7 +406,10 @@
 // --------------------------------------------------------------------------
 tOSSocketHandle SocketStream::GetSocketHandle()
 {
-	if(mSocketHandle == -1) {THROW_EXCEPTION(ServerException, BadSocketHandle)}
+	if(mSocketHandle == INVALID_SOCKET_VALUE) 
+	{
+		THROW_EXCEPTION(ServerException, BadSocketHandle)
+	}
 	return mSocketHandle;
 }
 


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:33:32 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:33:32 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.58a73639d6e15d78ffaa9150616ddbab@fluffy.co.uk>

--===============1738889250==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


eXN0ZW0gZm9yIFVOSVgu

--===============1738889250==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:39:24 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:39:24 +0100
Subject: [Box Backup-dev] COMMIT r907 - box/chris/merge/lib/server
Message-ID: <E1GIw84-000Agk-Uz@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:39:24 +0100 (Fri, 01 Sep 2006)
New Revision: 907

Modified:
   box/chris/merge/lib/server/SocketStreamTLS.cpp
Log:
Revert to trunk


Modified: box/chris/merge/lib/server/SocketStreamTLS.cpp
===================================================================
--- box/chris/merge/lib/server/SocketStreamTLS.cpp	2006-08-31 23:31:38 UTC (rev 906)
+++ box/chris/merge/lib/server/SocketStreamTLS.cpp	2006-08-31 23:39:24 UTC (rev 907)
@@ -137,12 +137,8 @@
 		THROW_EXCEPTION(ServerException, TLSAllocationFailed)
 	}
 
+#ifndef WIN32
 	// Make the socket non-blocking so timeouts on Read work
-
-#ifdef WIN32
-	u_long nonblocking = 1;
-	ioctlsocket(socket, FIONBIO, &nonblocking);
-#else // !WIN32
 	// This is more portable than using ioctl with FIONBIO
 	int statusFlags = 0;
 	if(::fcntl(socket, F_GETFL, &statusFlags) < 0
@@ -313,7 +309,7 @@
 
 		case SSL_ERROR_WANT_READ:
 		case SSL_ERROR_WANT_WRITE:
-			// wait for the required data
+			// wait for the requried data
 			// Will only get once around this loop, so don't need to calculate timeout values
 			if(WaitWhenRetryRequired(se, Timeout) == false)
 			{


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:40:18 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:40:18 +0100
Subject: [Box Backup-dev] COMMIT r908 - box/chris/merge/lib/server
Message-ID: <E1GIw8w-0000jw-4S@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:40:18 +0100 (Fri, 01 Sep 2006)
New Revision: 908

Modified:
   box/chris/merge/lib/server/SocketStreamTLS.cpp
Log:
(refs #3)

Added support for non-blocking sockets on Win32 as well


Modified: box/chris/merge/lib/server/SocketStreamTLS.cpp
===================================================================
--- box/chris/merge/lib/server/SocketStreamTLS.cpp	2006-08-31 23:39:24 UTC (rev 907)
+++ box/chris/merge/lib/server/SocketStreamTLS.cpp	2006-08-31 23:40:18 UTC (rev 908)
@@ -137,8 +137,12 @@
 		THROW_EXCEPTION(ServerException, TLSAllocationFailed)
 	}
 
-#ifndef WIN32
 	// Make the socket non-blocking so timeouts on Read work
+
+#ifdef WIN32
+	u_long nonblocking = 1;
+	ioctlsocket(socket, FIONBIO, &nonblocking);
+#else // !WIN32
 	// This is more portable than using ioctl with FIONBIO
 	int statusFlags = 0;
 	if(::fcntl(socket, F_GETFL, &statusFlags) < 0
@@ -309,7 +313,7 @@
 
 		case SSL_ERROR_WANT_READ:
 		case SSL_ERROR_WANT_WRITE:
-			// wait for the requried data
+			// wait for the required data
 			// Will only get once around this loop, so don't need to calculate timeout values
 			if(WaitWhenRetryRequired(se, Timeout) == false)
 			{


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:40:18 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:40:18 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.cabb0e90874568fe287905071d7e5964@fluffy.co.uk>

--===============1904008000==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


dXAgc3lzdGVtIGZvciBVTklYLg==

--===============1904008000==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:41:33 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:41:33 +0100
Subject: [Box Backup-dev] COMMIT r909 - box/chris/merge
Message-ID: <E1GIwA9-000LcU-8M@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:41:33 +0100 (Fri, 01 Sep 2006)
New Revision: 909

Modified:
   box/chris/merge/runtest.pl.in
Log:
Revert to trunk


Modified: box/chris/merge/runtest.pl.in
===================================================================
--- box/chris/merge/runtest.pl.in	2006-08-31 23:40:18 UTC (rev 908)
+++ box/chris/merge/runtest.pl.in	2006-08-31 23:41:33 UTC (rev 909)
@@ -1,14 +1,11 @@
 #!@PERL@
 
-use strict;
-use warnings;
-
 use lib 'infrastructure';
 use BoxPlatform;
 
 my ($test_name,$test_mode) = @ARGV;
 
-$test_mode = 'debug' if not defined $test_mode or $test_mode eq '';
+$test_mode = 'debug' if $test_mode eq '';
 
 if($test_name eq '' || ($test_mode ne 'debug' && $test_mode ne 'release'))
 {
@@ -20,26 +17,15 @@
 Mode defaults to debug.
 
 __E
-	exit(2);
+	exit(0);
 }
 
 my @results;
-my $exit_code = 0;
 
 if($test_name ne 'ALL')
 {
-	# run one or more specified test
-	if ($test_name =~ m/,/)
-	{
-		foreach my $test (split m/,/, $test_name)
-		{
-			runtest($test);
-		}
-	}
-	else
-	{
-		runtest($test_name);
-	}
+	# run one test
+	runtest($test_name);
 }
 else
 {
@@ -71,8 +57,6 @@
 # report results
 print "--------\n",join("\n",@results),"\n";
 
-exit $exit_code;
-
 sub runtest
 {
 	my ($t) = @_;
@@ -83,7 +67,6 @@
 	if($make_res != 0)
 	{
 		push @results,"$t: make failed";
-		$exit_code = 2;
 		return;
 	}
 	
@@ -99,14 +82,8 @@
 			$last = $_ if m/\w/;
 		}
 		close RESULTS;
-
 		chomp $last;
 		push @results,"$t: $last";
-
-		if ($last ne "PASSED") 
-		{ 
-			$exit_code = 1;
-		}
 	}
 	else
 	{


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:43:40 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 00:43:40 +0100
Subject: [Box Backup-dev] COMMIT r910 - box/chris/merge
Message-ID: <E1GIwCC-0008bS-HD@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 00:43:40 +0100 (Fri, 01 Sep 2006)
New Revision: 910

Modified:
   box/chris/merge/runtest.pl.in
Log:
(refs #3)

Use strict and warnings for code safety.

Exit with status 2 on invalid usage.

Allow running multiple tests, comma separated.

Exit with status 2 if make fails for any test.

Exit with status 1 if any test fails.


Modified: box/chris/merge/runtest.pl.in
===================================================================
--- box/chris/merge/runtest.pl.in	2006-08-31 23:41:33 UTC (rev 909)
+++ box/chris/merge/runtest.pl.in	2006-08-31 23:43:40 UTC (rev 910)
@@ -1,11 +1,14 @@
 #!@PERL@
 
+use strict;
+use warnings;
+
 use lib 'infrastructure';
 use BoxPlatform;
 
 my ($test_name,$test_mode) = @ARGV;
 
-$test_mode = 'debug' if $test_mode eq '';
+$test_mode = 'debug' if not defined $test_mode or $test_mode eq '';
 
 if($test_name eq '' || ($test_mode ne 'debug' && $test_mode ne 'release'))
 {
@@ -17,15 +20,26 @@
 Mode defaults to debug.
 
 __E
-	exit(0);
+	exit(2);
 }
 
 my @results;
+my $exit_code = 0;
 
 if($test_name ne 'ALL')
 {
-	# run one test
-	runtest($test_name);
+	# run one or more specified test
+	if ($test_name =~ m/,/)
+	{
+		foreach my $test (split m/,/, $test_name)
+		{
+			runtest($test);
+		}
+	}
+	else
+	{
+		runtest($test_name);
+	}
 }
 else
 {
@@ -57,6 +71,8 @@
 # report results
 print "--------\n",join("\n",@results),"\n";
 
+exit $exit_code;
+
 sub runtest
 {
 	my ($t) = @_;
@@ -67,6 +83,7 @@
 	if($make_res != 0)
 	{
 		push @results,"$t: make failed";
+		$exit_code = 2;
 		return;
 	}
 	
@@ -82,8 +99,14 @@
 			$last = $_ if m/\w/;
 		}
 		close RESULTS;
+
 		chomp $last;
 		push @results,"$t: $last";
+
+		if ($last ne "PASSED") 
+		{ 
+			$exit_code = 1;
+		}
 	}
 	else
 	{


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 00:43:40 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Thu, 31 Aug 2006 23:43:40 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.206652cb5f601cec5c8c239c036b08de@fluffy.co.uk>

--===============0855600339==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


cCBzeXN0ZW0gZm9yIFVOSVgu

--===============0855600339==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:48:13 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:48:13 +0100
Subject: [Box Backup-dev] COMMIT r911 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3l7-000LxC-3Z@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:48:12 +0100 (Fri, 01 Sep 2006)
New Revision: 911

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
Revert to trunk


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-08-31 23:43:40 UTC (rev 910)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:48:12 UTC (rev 911)
@@ -66,21 +66,23 @@
 
 void make_file_of_zeros(const char *filename, size_t size)
 {
-	#ifdef WIN32
-	HANDLE handle = openfile(filename, O_WRONLY | O_CREAT | O_EXCL, 0);
-	TEST_THAT(handle != INVALID_HANDLE_VALUE);
-	SetFilePointer(handle, size, NULL, FILE_BEGIN);
-	TEST_THAT(GetLastError() == NO_ERROR);
-	TEST_THAT(SetEndOfFile(handle) == true);
-	TEST_THAT(CloseHandle(handle)  == true);
-	#else
-	int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
-	if (fd < 0) perror(filename);
-	TEST_THAT(fd >= 0);
-	TEST_THAT(ftruncate(fd, size) == 0);
-	TEST_THAT(close(fd) == 0);
-	#endif
+	static const size_t bs = 0x10000;
+	size_t remSize = size;
+	void *b = malloc(bs);
+	memset(b, 0, bs);
+	FILE *f = fopen(filename, "wb");
 
+	// Using largish blocks like this is much faster, while not consuming too much RAM
+	while(remSize > bs)
+	{
+		fwrite(b, bs, 1, f);
+		remSize -= bs;
+	}
+	fwrite(b, remSize, 1, f);
+
+	fclose(f);
+	free(b);
+
 	TEST_THAT((size_t)TestGetFileSize(filename) == size);
 }
 
@@ -116,20 +118,12 @@
 		if(s > 0)
 		{
 			nnew++;
-			#ifdef WIN32
-			TRACE2("%8I64d this  s=%8I64d", b, s);
-			#else
 			TRACE2("%8lld this  s=%8lld", b, s);
-			#endif
 		}
 		else
 		{
 			nold++;
-			#ifdef WIN32
-			TRACE2("%8I64d other i=%8I64d", b, 0 - s);		
-			#else
 			TRACE2("%8lld other i=%8lld", b, 0 - s);		
-			#endif
 		}
 		// Decode the rest
 		uint64_t iv = box_ntoh64(hdr.mEntryIVBase);
@@ -213,18 +207,10 @@
 	}
 	else
 	{
-#ifdef WIN32
 		// Emulate the above stage!
-		char src[256], dst[256];
-		sprintf(src, "testfiles\\f%d.diff", to);
-		sprintf(dst, "testfiles\\f%d.encoded", to);
-		TEST_THAT(CopyFile(src, dst, FALSE) != 0)
-#else
-		// Emulate the above stage!
 		char cmd[256];
 		sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to);
 		::system(cmd);
-#endif
 	}
 
 	// Decode it
@@ -369,10 +355,8 @@
 {
 	// Want to trace out all the details
 	#ifndef NDEBUG
-	#ifndef WIN32
 	BackupStoreFile::TraceDetailsOfDiffProcess = true;
 	#endif
-	#endif
 
 	// Create all the test files
 	create_test_files();
@@ -386,7 +370,6 @@
 		FileStream out("testfiles/f0.encoded", O_WRONLY | O_CREAT | O_EXCL);
 		std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/f0", 1 /* dir ID */, f0name));
 		encoded->CopyStreamTo(out);
-		out.Close();
 		check_encoded_file("testfiles/f0.encoded", 0, 33, 0);
 	}
 	
@@ -447,7 +430,6 @@
 			FileStream out("testfiles/f9.zerotest", O_WRONLY | O_CREAT | O_EXCL);
 			std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/f9", 1 /* dir ID */, fn));
 			encoded->CopyStreamTo(out);
-			out.Close();
 			check_encoded_file("testfiles/f9.zerotest", 0, 0, 0);		
 		}
 		{
@@ -458,7 +440,6 @@
 		}
 	}
 	
-#ifndef WIN32	
 	// Check that symlinks aren't diffed
 	TEST_THAT(::symlink("f2", "testfiles/f2.symlink") == 0)
 	// And go and diff it against the previous encoded file
@@ -486,10 +467,8 @@
 		TEST_THAT(completelyDifferent == true);
 		check_encoded_file("testfiles/f2.symlink.diff", 0, 0, 0);		
 	}
-#endif
 
-	// Check that diffing against a file which isn't "complete" and 
-	// references another isn't allowed
+	// Check that diffing against a file which isn't "complete" and referes another isn't allowed
 	{
 		FileStream blockindex("testfiles/f1.diff");
 		BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
@@ -501,19 +480,10 @@
 			0, 0), BackupStoreException, CannotDiffAnIncompleteStoreFile);
 	}
 
-	// Found a nasty case where files of lots of the same thing 
-	// suck up lots of processor time -- because of lots of matches 
-	// found. Check this out!
-
-	#ifdef WIN32
-	::fprintf(stdout, "Testing diffing two large streams, "
-		"may take a while!\n");
-	::fflush(stdout);
-	#endif
-
+	// Found a nasty case where files of lots of the same thing sock up lots of processor
+	// time -- because of lots of matches found. Check this out!
 	make_file_of_zeros("testfiles/zero.0", 20*1024*1024);
 	make_file_of_zeros("testfiles/zero.1", 200*1024*1024);
-
 	// Generate a first encoded file
 	{
 		BackupStoreFilenameClear f0name("zero.0");
@@ -533,14 +503,7 @@
 			2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite,
 			0, 0));
 		encoded->CopyStreamTo(out);
-
-		printf("Time taken: %d seconds\n", (int)(time(0) - beginTime));
-
-		#ifdef WIN32
-		TEST_THAT(time(0) < (beginTime + 300));
-		#else
 		TEST_THAT(time(0) < (beginTime + 40));
-		#endif
 	}
 	// Remove zero-files to save disk space
 	remove("testfiles/zero.0");


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:49:31 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:49:31 +0100
Subject: [Box Backup-dev] COMMIT r912 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3mN-000FEx-Rb@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:49:31 +0100 (Fri, 01 Sep 2006)
New Revision: 912

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3)

Use truncation/sparse files to quickly create files with 200MB of zeroes


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:48:12 UTC (rev 911)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:49:31 UTC (rev 912)
@@ -66,23 +66,21 @@
 
 void make_file_of_zeros(const char *filename, size_t size)
 {
-	static const size_t bs = 0x10000;
-	size_t remSize = size;
-	void *b = malloc(bs);
-	memset(b, 0, bs);
-	FILE *f = fopen(filename, "wb");
+	#ifdef WIN32
+	HANDLE handle = openfile(filename, O_WRONLY | O_CREAT | O_EXCL, 0);
+	TEST_THAT(handle != INVALID_HANDLE_VALUE);
+	SetFilePointer(handle, size, NULL, FILE_BEGIN);
+	TEST_THAT(GetLastError() == NO_ERROR);
+	TEST_THAT(SetEndOfFile(handle) == true);
+	TEST_THAT(CloseHandle(handle)  == true);
+	#else
+	int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
+	if (fd < 0) perror(filename);
+	TEST_THAT(fd >= 0);
+	TEST_THAT(ftruncate(fd, size) == 0);
+	TEST_THAT(close(fd) == 0);
+	#endif
 
-	// Using largish blocks like this is much faster, while not consuming too much RAM
-	while(remSize > bs)
-	{
-		fwrite(b, bs, 1, f);
-		remSize -= bs;
-	}
-	fwrite(b, remSize, 1, f);
-
-	fclose(f);
-	free(b);
-
 	TEST_THAT((size_t)TestGetFileSize(filename) == size);
 }
 


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:49:32 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 07:49:32 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.bd0c4b3f5c2d530a668bae3ecddaa659@fluffy.co.uk>

--===============2140675380==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


YXRpYyBvbi1saW5lIGJhY2t1cCBzeXN0ZW0gZm9yIFVOSVgu

--===============2140675380==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:50:47 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:50:47 +0100
Subject: [Box Backup-dev] COMMIT r913 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3nb-00053d-HK@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:50:47 +0100 (Fri, 01 Sep 2006)
New Revision: 913

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3)

64-bit format string fixes for Win32


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:49:31 UTC (rev 912)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:50:47 UTC (rev 913)
@@ -116,12 +116,20 @@
 		if(s > 0)
 		{
 			nnew++;
+			#ifdef WIN32
+			TRACE2("%8I64d this  s=%8I64d", b, s);
+			#else
 			TRACE2("%8lld this  s=%8lld", b, s);
+			#endif
 		}
 		else
 		{
 			nold++;
+			#ifdef WIN32
+			TRACE2("%8I64d other i=%8I64d", b, 0 - s);		
+			#else
 			TRACE2("%8lld other i=%8lld", b, 0 - s);		
+			#endif
 		}
 		// Decode the rest
 		uint64_t iv = box_ntoh64(hdr.mEntryIVBase);


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:50:47 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 07:50:47 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.fd884f57a17b65130bdaa655894d22b8@fluffy.co.uk>

--===============0474059416==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============0474059416==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:51:46 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:51:46 +0100
Subject: [Box Backup-dev] COMMIT r914 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3oY-000NKf-2e@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:51:45 +0100 (Fri, 01 Sep 2006)
New Revision: 914

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3)

Use CopyFile to copy on Windows, instead of shelling out to cp, which
doesn't understand native paths.


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:50:47 UTC (rev 913)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:51:45 UTC (rev 914)
@@ -213,10 +213,18 @@
 	}
 	else
 	{
+#ifdef WIN32
 		// Emulate the above stage!
+		char src[256], dst[256];
+		sprintf(src, "testfiles\\f%d.diff", to);
+		sprintf(dst, "testfiles\\f%d.encoded", to);
+		TEST_THAT(CopyFile(src, dst, FALSE) != 0)
+#else
+		// Emulate the above stage!
 		char cmd[256];
 		sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to);
 		::system(cmd);
+#endif
 	}
 
 	// Decode it


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:51:47 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 07:51:47 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.c42e511223eef2902b6b0f06698dd428@fluffy.co.uk>

--===============1164882398==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


bSBmb3IgVU5JWC4=

--===============1164882398==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:52:51 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:52:51 +0100
Subject: [Box Backup-dev] COMMIT r915 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3pb-000Ebm-Bo@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:52:51 +0100 (Fri, 01 Sep 2006)
New Revision: 915

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3)

Close open file handles on Win32


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:51:45 UTC (rev 914)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:52:51 UTC (rev 915)
@@ -384,6 +384,7 @@
 		FileStream out("testfiles/f0.encoded", O_WRONLY | O_CREAT | O_EXCL);
 		std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/f0", 1 /* dir ID */, f0name));
 		encoded->CopyStreamTo(out);
+		out.Close();
 		check_encoded_file("testfiles/f0.encoded", 0, 33, 0);
 	}
 	
@@ -444,6 +445,7 @@
 			FileStream out("testfiles/f9.zerotest", O_WRONLY | O_CREAT | O_EXCL);
 			std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/f9", 1 /* dir ID */, fn));
 			encoded->CopyStreamTo(out);
+			out.Close();
 			check_encoded_file("testfiles/f9.zerotest", 0, 0, 0);		
 		}
 		{


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:52:51 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 07:52:51 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.5c146a98a60a70e19f93041f80a4ccd7@fluffy.co.uk>

--===============1436594657==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============1436594657==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:54:40 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:54:40 +0100
Subject: [Box Backup-dev] COMMIT r916 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3rM-000F68-8P@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:54:40 +0100 (Fri, 01 Sep 2006)
New Revision: 916

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3)

Completely disable symlink test on Win32


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:52:51 UTC (rev 915)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:54:40 UTC (rev 916)
@@ -456,6 +456,7 @@
 		}
 	}
 	
+#ifndef WIN32	
 	// Check that symlinks aren't diffed
 	TEST_THAT(::symlink("f2", "testfiles/f2.symlink") == 0)
 	// And go and diff it against the previous encoded file
@@ -483,6 +484,7 @@
 		TEST_THAT(completelyDifferent == true);
 		check_encoded_file("testfiles/f2.symlink.diff", 0, 0, 0);		
 	}
+#endif
 
 	// Check that diffing against a file which isn't "complete" and referes another isn't allowed
 	{


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:54:40 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 07:54:40 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.908862939b150175ae736e157412b8eb@fluffy.co.uk>

--===============0631137031==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


TklYLg==

--===============0631137031==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:57:05 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:57:05 +0100
Subject: [Box Backup-dev] COMMIT r917 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3th-000Ea3-Dn@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 08:57:05 +0100 (Fri, 01 Sep 2006)
New Revision: 917

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3)

Cosmetic fixes to comments


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:54:40 UTC (rev 916)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:57:05 UTC (rev 917)
@@ -486,7 +486,8 @@
 	}
 #endif
 
-	// Check that diffing against a file which isn't "complete" and referes another isn't allowed
+	// Check that diffing against a file which isn't "complete" and 
+	// references another isn't allowed
 	{
 		FileStream blockindex("testfiles/f1.diff");
 		BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
@@ -498,8 +499,10 @@
 			0, 0), BackupStoreException, CannotDiffAnIncompleteStoreFile);
 	}
 
-	// Found a nasty case where files of lots of the same thing sock up lots of processor
-	// time -- because of lots of matches found. Check this out!
+	// Found a nasty case where files of lots of the same thing 
+	// suck up lots of processor time -- because of lots of matches 
+	// found. Check this out!
+
 	make_file_of_zeros("testfiles/zero.0", 20*1024*1024);
 	make_file_of_zeros("testfiles/zero.1", 200*1024*1024);
 	// Generate a first encoded file


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 08:57:05 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 07:57:05 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.20f98fb5ae55cf31847c21619b7b8901@fluffy.co.uk>

--===============1300729770==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============1300729770==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:00:04 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 09:00:04 +0100
Subject: [Box Backup-dev] COMMIT r918 - box/chris/merge/test/backupdiff
Message-ID: <E1GJ3wa-0004Fz-Dw@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 09:00:04 +0100 (Fri, 01 Sep 2006)
New Revision: 918

Modified:
   box/chris/merge/test/backupdiff/testbackupdiff.cpp
Log:
(refs #3) 

This test takes a long time on Win32 (slow file access? VMware?), so:

 * Disable verbose debug logging, which makes it even slower
 * Print a warning before the slow test starts
 * Increase timeout


Modified: box/chris/merge/test/backupdiff/testbackupdiff.cpp
===================================================================
--- box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 07:57:05 UTC (rev 917)
+++ box/chris/merge/test/backupdiff/testbackupdiff.cpp	2006-09-01 08:00:04 UTC (rev 918)
@@ -369,8 +369,10 @@
 {
 	// Want to trace out all the details
 	#ifndef NDEBUG
+	#ifndef WIN32
 	BackupStoreFile::TraceDetailsOfDiffProcess = true;
 	#endif
+	#endif
 
 	// Create all the test files
 	create_test_files();
@@ -503,8 +505,15 @@
 	// suck up lots of processor time -- because of lots of matches 
 	// found. Check this out!
 
+	#ifdef WIN32
+	::fprintf(stdout, "Testing diffing two large streams, "
+		"may take a while!\n");
+	::fflush(stdout);
+	#endif
+
 	make_file_of_zeros("testfiles/zero.0", 20*1024*1024);
 	make_file_of_zeros("testfiles/zero.1", 200*1024*1024);
+
 	// Generate a first encoded file
 	{
 		BackupStoreFilenameClear f0name("zero.0");
@@ -524,7 +533,14 @@
 			2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite,
 			0, 0));
 		encoded->CopyStreamTo(out);
+
+		printf("Time taken: %d seconds\n", (int)(time(0) - beginTime));
+
+		#ifdef WIN32
+		TEST_THAT(time(0) < (beginTime + 300));
+		#else
 		TEST_THAT(time(0) < (beginTime + 40));
+		#endif
 	}
 	// Remove zero-files to save disk space
 	remove("testfiles/zero.0");


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:00:05 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:00:05 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.d67f84032f9862efbcf21acdf8055fee@fluffy.co.uk>

--===============1760158957==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============1760158957==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:03:32 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 09:03:32 +0100
Subject: [Box Backup-dev] COMMIT r919 - box/chris/merge/test/backupstore
Message-ID: <E1GJ3zw-0002Nf-Re@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 09:03:32 +0100 (Fri, 01 Sep 2006)
New Revision: 919

Modified:
   box/chris/merge/test/backupstore/testbackupstore.cpp
Log:
Revert to trunk


Modified: box/chris/merge/test/backupstore/testbackupstore.cpp
===================================================================
--- box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:00:04 UTC (rev 918)
+++ box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:03:32 UTC (rev 919)
@@ -425,8 +425,7 @@
 	}
 	
 	free(data);
-	in.Close();
-	TEST_THAT(unlink("testfiles/test_download") == 0);
+	unlink("testfiles/test_download");
 }
 
 void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)
@@ -931,7 +930,6 @@
 		// Check marker is 0
 		TEST_THAT(loginConf->GetClientStoreMarker() == 0);
 
-#ifndef WIN32
 		// Check that we can't open a new connection which requests write permissions
 		{
 			SocketStreamTLS conn;
@@ -943,12 +941,10 @@
 				ConnectionException, Conn_Protocol_UnexpectedReply);
 			protocol.QueryFinished();
 		}
-#endif
 		
 		// Set the client store marker
 		protocol.QuerySetClientStoreMarker(0x8732523ab23aLL);
 
-#ifndef WIN32
 		// Open a new connection which is read only
 		SocketStreamTLS connReadOnly;
 		connReadOnly.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED);
@@ -967,11 +963,10 @@
 			// Check client store marker
 			TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL);
 		}
-#else // WIN32
-		BackupProtocolClient& protocolReadOnly(protocol);
-#endif
 
 		test_server_1(protocol, protocolReadOnly);
+
+
 		// Create and upload some test files
 		int64_t maxID = 0;
 		for(int t = 0; t < UPLOAD_NUM; ++t)
@@ -1443,15 +1438,11 @@
 		}
 			
 		// Finish the connections
-#ifndef WIN32
 		protocolReadOnly.QueryFinished();
-#endif
 		protocol.QueryFinished();
 		
 		// Close logs
-#ifndef WIN32
 		::fclose(protocolReadOnlyLog);
-#endif
 		::fclose(protocolLog);
 	}
 	
@@ -1529,48 +1520,35 @@
 		
 		// The test block to a file
 		{
-			FileStream f("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1", O_WRONLY | O_CREAT | O_EXCL);
+			FileStream f("testfiles/testenc1", O_WRONLY | O_CREAT | O_EXCL);
 			f.Write(encfile, sizeof(encfile));
 		}
 		
 		// Encode it
 		{
-			FileStream out("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1_enc", O_WRONLY | O_CREAT | O_EXCL);
-			BackupStoreFilenameClear name("testfiles"
-				DIRECTORY_SEPARATOR "testenc1");
+			FileStream out("testfiles/testenc1_enc", O_WRONLY | O_CREAT | O_EXCL);
+			BackupStoreFilenameClear name("testfiles/testenc1");
 
-			std::auto_ptr<IOStream> encoded(
-				BackupStoreFile::EncodeFile(
-					"testfiles" DIRECTORY_SEPARATOR
-					"testenc1", 32, name));
+			std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testenc1", 32, name));
 			encoded->CopyStreamTo(out);
 		}
 		
 		// Verify it
 		{
-			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1_enc");
+			FileStream enc("testfiles/testenc1_enc");
 			TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc) == true);
 		}
 		
 		// Decode it
 		{
-			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1_enc");
-			BackupStoreFile::DecodeFile(enc, "testfiles"
-				DIRECTORY_SEPARATOR "testenc1_orig", 
-				IOStream::TimeOutInfinite);
+			FileStream enc("testfiles/testenc1_enc");
+			BackupStoreFile::DecodeFile(enc, "testfiles/testenc1_orig", IOStream::TimeOutInfinite);
 		}
 		
 		// Read in rebuilt original, and compare contents
 		{
-			TEST_THAT(TestGetFileSize("testfiles" 
-				DIRECTORY_SEPARATOR "testenc1_orig") 
-				== sizeof(encfile));
-			FileStream in("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1_orig");
+			TEST_THAT(TestGetFileSize("testfiles/testenc1_orig") == sizeof(encfile));
+			FileStream in("testfiles/testenc1_orig");
 			int encfile_i[ENCFILE_SIZE];
 			in.Read(encfile_i, sizeof(encfile_i));
 			TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0);
@@ -1578,8 +1556,7 @@
 		
 		// Check how many blocks it had, and test the stream based interface
 		{
-			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1_enc");
+			FileStream enc("testfiles/testenc1_enc");
 			std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(enc, IOStream::TimeOutInfinite));
 			CollectInBufferStream d;
 			decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */);
@@ -1593,15 +1570,10 @@
 		// Test that the last block in a file, if less than 256 bytes, gets put into the last block
 		{
 			#define FILE_SIZE_JUST_OVER	((4096*2)+58)
-			FileStream f("testfiles" DIRECTORY_SEPARATOR 
-				"testenc2", O_WRONLY | O_CREAT | O_EXCL);
+			FileStream f("testfiles/testenc2", O_WRONLY | O_CREAT | O_EXCL);
 			f.Write(encfile + 2, FILE_SIZE_JUST_OVER);
-			f.Close();
 			BackupStoreFilenameClear name("testenc2");
-			std::auto_ptr<IOStream> encoded(
-				BackupStoreFile::EncodeFile(
-					"testfiles" DIRECTORY_SEPARATOR
-					"testenc2", 32, name));
+			std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testenc2", 32, name));
 			CollectInBufferStream e;
 			encoded->CopyStreamTo(e);
 			e.SetForReading();
@@ -1617,8 +1589,7 @@
 		
 		// Test that reordered streams work too
 		{
-			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
-				"testenc1_enc");
+			FileStream enc("testfiles/testenc1_enc");
 			std::auto_ptr<IOStream> reordered(BackupStoreFile::ReorderFileToStreamOrder(&enc, false));
 			std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(*reordered, IOStream::TimeOutInfinite));
 			CollectInBufferStream d;
@@ -1630,7 +1601,6 @@
 			TEST_THAT(decoded->GetNumBlocks() == 3);
 		}
 		
-#ifndef WIN32		
 		// Try out doing this on a symlink
 		{
 			TEST_THAT(::symlink("does/not/exist", "testfiles/testsymlink") == 0);
@@ -1644,20 +1614,14 @@
 			// Decode it
 			BackupStoreFile::DecodeFile(b, "testfiles/testsymlink_2", IOStream::TimeOutInfinite);
 		}
-#endif
 	}
 
 	// Store info
 	{
 		RaidFileWrite::CreateDirectory(0, "test-info");
-		BackupStoreInfo::CreateNew(76, "test-info" DIRECTORY_SEPARATOR, 
-			0, 3461231233455433LL, 2934852487LL);
-		TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, 
-			"test-info" DIRECTORY_SEPARATOR, 0, 0, 0), 
-			RaidFileException, CannotOverwriteExistingFile);
-		std::auto_ptr<BackupStoreInfo> info(
-			BackupStoreInfo::Load(76, 
-				"test-info" DIRECTORY_SEPARATOR, 0, true));
+		BackupStoreInfo::CreateNew(76, "test-info/", 0, 3461231233455433LL, 2934852487LL);
+		TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, "test-info/", 0, 0, 0), RaidFileException, CannotOverwriteExistingFile);
+		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, true));
 		TEST_CHECK_THROWS(info->Save(), BackupStoreException, StoreInfoIsReadOnly);
 		TEST_CHECK_THROWS(info->ChangeBlocksUsed(1), BackupStoreException, StoreInfoIsReadOnly);
 		TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(1), BackupStoreException, StoreInfoIsReadOnly);
@@ -1666,8 +1630,7 @@
 		TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly);
 	}
 	{
-		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, 
-			"test-info" DIRECTORY_SEPARATOR, 0, false));
+		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, false));
 		info->ChangeBlocksUsed(8);
 		info->ChangeBlocksInOldFiles(9);
 		info->ChangeBlocksInDeletedFiles(10);
@@ -1685,8 +1648,7 @@
 		info->Save();
 	}
 	{
-		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, 
-			"test-info" DIRECTORY_SEPARATOR, 0, true));
+		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, true));
 		TEST_THAT(info->GetBlocksUsed() == 7);
 		TEST_THAT(info->GetBlocksInOldFiles() == 5);
 		TEST_THAT(info->GetBlocksInDeletedFiles() == 1);
@@ -1704,18 +1666,12 @@
 	// Context
 	TLSContext context;
 	context.Initialise(false /* client */,
-			"testfiles" DIRECTORY_SEPARATOR "clientCerts.pem",
-			"testfiles" DIRECTORY_SEPARATOR "clientPrivKey.pem",
-			"testfiles" DIRECTORY_SEPARATOR "clientTrustedCAs.pem");
+			"testfiles/clientCerts.pem",
+			"testfiles/clientPrivKey.pem",
+			"testfiles/clientTrustedCAs.pem");
 
 	// First, try logging in without an account having been created... just make sure login fails.
-
-#ifdef WIN32
-	int pid = LaunchServer("..\\..\\bin\\bbstored\\bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid");
-#else
 	int pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid");
-#endif
-
 	TEST_THAT(pid != -1 && pid != 0);
 	if(pid > 0)
 	{
@@ -1744,13 +1700,8 @@
 		}
 
 		// Create an account for the test client
-#ifdef WIN32
-		TEST_THAT_ABORTONFAIL(::system("..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts -c testfiles/bbstored.conf create 01234567 0 10000B 20000B") == 0);
-#else
 		TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf create 01234567 0 10000B 20000B") == 0);
 		TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
-#endif
-
 		TEST_THAT(TestDirExists("testfiles/0_0/backup/01234567"));
 		TEST_THAT(TestDirExists("testfiles/0_1/backup/01234567"));
 		TEST_THAT(TestDirExists("testfiles/0_2/backup/01234567"));
@@ -1774,27 +1725,15 @@
 		TEST_THAT(KillServer(pid));
 		::sleep(1);
 		TEST_THAT(!ServerIsAlive(pid));
-#ifndef WIN32
 		TestRemoteProcessMemLeaks("bbstored.memleaks");
-#endif
 		
 		// Set a new limit on the account -- leave the hard limit high to make sure the target for
 		// freeing space is the soft limit.
-
-#ifdef WIN32
-		TEST_THAT_ABORTONFAIL(::system("..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 20000B") == 0);
-#else
 		TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 20000B") == 0);
 		TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
-#endif
 
 		// Start things up
-#ifdef WIN32
-		pid = LaunchServer("..\\..\\bin\\bbstored\\bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid");
-#else
 		pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid");
-#endif
-
 		::sleep(1);
 		TEST_THAT(ServerIsAlive(pid));
 		
@@ -1819,12 +1758,8 @@
 		TEST_THAT(after.old == 0);
 		
 		// Set a really small hard limit
-#ifdef WIN32
-		TEST_THAT_ABORTONFAIL(::system("..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 20B") == 0);
-#else
 		TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 20B") == 0);
 		TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
-#endif
 
 		// Try to upload a file and create a directory, and check an error is generated
 		{
@@ -1873,10 +1808,7 @@
 		TEST_THAT(KillServer(pid));
 		::sleep(1);
 		TEST_THAT(!ServerIsAlive(pid));
-
-#ifndef WIN32
 		TestRemoteProcessMemLeaks("bbstored.memleaks");
-#endif
 	}
 
 	return 0;
@@ -1888,19 +1820,10 @@
 
 	// Create an account for the test client
 	TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf create 01234567 0 30000B 40000B") == 0);
-
-#ifndef WIN32
 	TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
-#endif
 
 	// First, try logging in without an account having been created... just make sure login fails.
-
-#ifdef WIN32
-	int pid = LaunchServer("..\\..\\bin\\bbstored\\bbstored testfiles/bbstored_multi.conf", "testfiles/bbstored.pid");
-#else
 	int pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored_multi.conf", "testfiles/bbstored.pid");
-#endif
-
 	TEST_THAT(pid != -1 && pid != 0);
 	if(pid > 0)
 	{
@@ -1917,63 +1840,15 @@
 		TEST_THAT(KillServer(pid));
 		::sleep(1);
 		TEST_THAT(!ServerIsAlive(pid));
-#ifndef WIN32
 		TestRemoteProcessMemLeaks("bbstored.memleaks");
-#endif
 	}
 
 
 	return 0;
 }
 
-#ifdef WIN32
-WCHAR* ConvertUtf8ToWideString(const char* pString);
-std::string ConvertPathToAbsoluteUnicode(const char *pFileName);
-#endif
-
 int test(int argc, const char *argv[])
 {
-#ifdef WIN32
-	// Under win32 we must initialise the Winsock library
-	// before using sockets
-
-	WSADATA info;
-	TEST_THAT(WSAStartup(0x0101, &info) != SOCKET_ERROR)
-
-	// this had better work, or bbstored will die when combining diffs
-	char* file = "foo";
-	std::string abs = ConvertPathToAbsoluteUnicode(file);
-	WCHAR* wfile = ConvertUtf8ToWideString(abs.c_str());
-
-	DWORD accessRights = FILE_READ_ATTRIBUTES | 
-		FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_WRITE_ATTRIBUTES |
-		FILE_WRITE_DATA | FILE_WRITE_EA /*| FILE_ALL_ACCESS*/;
-	DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
-
-	HANDLE h1 = CreateFileW(wfile, accessRights, shareMode,
-		NULL, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-	assert(h1 != INVALID_HANDLE_VALUE);
-	TEST_THAT(h1 != INVALID_HANDLE_VALUE);
-
-	accessRights = FILE_READ_ATTRIBUTES | 
-		FILE_LIST_DIRECTORY | FILE_READ_EA;
-
-	HANDLE h2 = CreateFileW(wfile, accessRights, shareMode,
-		NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-	assert(h2 != INVALID_HANDLE_VALUE);
-	TEST_THAT(h2 != INVALID_HANDLE_VALUE);
-
-	CloseHandle(h2);
-	CloseHandle(h1);
-
-	h1 = openfile("foo", O_CREAT | O_RDWR, 0);
-	TEST_THAT(h1 != INVALID_HANDLE_VALUE);
-	h2 = openfile("foo", O_RDWR, 0);
-	TEST_THAT(h2 != INVALID_HANDLE_VALUE);
-	CloseHandle(h2);
-	CloseHandle(h1);
-#endif
-
 	// SSL library
 	SSLLib::Initialise();
 	
@@ -1990,11 +1865,7 @@
 
 	// Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used
 	// for seeing what's going on.
-#ifdef WIN32
-	BackupClientCryptoKeys_Setup("testfiles\\bbackupd.keys");	
-#else
 	BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys");	
-#endif
 	
 	// encode in some filenames -- can't do static initialisation because the key won't be set up when these are initialised
 	for(unsigned int l = 0; l < sizeof(ens_filenames) / sizeof(ens_filenames[0]); ++l)


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:05:29 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 09:05:29 +0100
Subject: [Box Backup-dev] COMMIT r920 - box/chris/merge/test/backupstore
Message-ID: <E1GJ41p-000LGg-Q5@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 09:05:29 +0100 (Fri, 01 Sep 2006)
New Revision: 920

Modified:
   box/chris/merge/test/backupstore/testbackupstore.cpp
Log:
(refs #3)

Close before unlink

Check that unlink succeeds


Modified: box/chris/merge/test/backupstore/testbackupstore.cpp
===================================================================
--- box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:03:32 UTC (rev 919)
+++ box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:05:29 UTC (rev 920)
@@ -425,7 +425,8 @@
 	}
 	
 	free(data);
-	unlink("testfiles/test_download");
+	in.Close();
+	TEST_THAT(unlink("testfiles/test_download") == 0);
 }
 
 void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:05:30 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:05:30 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.6f750e159b3033250e5a0acebc52004c@fluffy.co.uk>

--===============1719374369==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


c3RlbSBmb3IgVU5JWC4=

--===============1719374369==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:02:12 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 09:02:12 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <1157064778.26754.12.camel@avenin.ebourne.me.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk>
Message-ID: <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk>

On 31 Aug 2006, at 23:52, Martin Ebourne wrote:

> Given the amount of autogenerated traffic now hitting the -dev list I
> wonder if it's time for a -commit list for all trac/svn traffic? That
> way interested people could monitor the -dev list without being  
> spammed.
> Presumably the main developers would keep both though.

Good plan. I'll get this set up as soon as I can.

Ben





From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:08:11 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 09:08:11 +0100
Subject: [Box Backup-dev] COMMIT r921 - box/chris/merge/test/backupstore
Message-ID: <E1GJ44R-000MRX-UM@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 09:08:11 +0100 (Fri, 01 Sep 2006)
New Revision: 921

Modified:
   box/chris/merge/test/backupstore/testbackupstore.cpp
Log:
(refs #3)

We cannot open multiple connections to the server on Win32, so work around.


Modified: box/chris/merge/test/backupstore/testbackupstore.cpp
===================================================================
--- box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:05:29 UTC (rev 920)
+++ box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:08:11 UTC (rev 921)
@@ -931,6 +931,7 @@
 		// Check marker is 0
 		TEST_THAT(loginConf->GetClientStoreMarker() == 0);
 
+#ifndef WIN32
 		// Check that we can't open a new connection which requests write permissions
 		{
 			SocketStreamTLS conn;
@@ -942,10 +943,12 @@
 				ConnectionException, Conn_Protocol_UnexpectedReply);
 			protocol.QueryFinished();
 		}
+#endif
 		
 		// Set the client store marker
 		protocol.QuerySetClientStoreMarker(0x8732523ab23aLL);
 
+#ifndef WIN32
 		// Open a new connection which is read only
 		SocketStreamTLS connReadOnly;
 		connReadOnly.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED);
@@ -964,10 +967,12 @@
 			// Check client store marker
 			TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL);
 		}
+#else // WIN32
+		BackupProtocolClient& protocolReadOnly(protocol);
+#endif
 
 		test_server_1(protocol, protocolReadOnly);
 
-
 		// Create and upload some test files
 		int64_t maxID = 0;
 		for(int t = 0; t < UPLOAD_NUM; ++t)
@@ -1439,11 +1444,15 @@
 		}
 			
 		// Finish the connections
+#ifndef WIN32
 		protocolReadOnly.QueryFinished();
+#endif
 		protocol.QueryFinished();
 		
 		// Close logs
+#ifndef WIN32
 		::fclose(protocolReadOnlyLog);
+#endif
 		::fclose(protocolLog);
 	}
 	


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:08:12 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:08:12 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.23c1e15f5832e5592a312da355b64566@fluffy.co.uk>

--===============2139851756==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


YXV0b21hdGljIG9uLWxpbmUgYmFja3VwIHN5c3RlbSBmb3IgVU5JWC4=

--===============2139851756==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:07:04 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 09:07:04 +0100
Subject: [Box Backup-dev] changeset [819]
In-Reply-To: <Pine.LNX.4.62.0609010017450.28816@top.qwarx.com>
References: <1156975704.2413.1.camel@avenin.ebourne.me.uk>  <Pine.LNX.4.62.0608310904090.28816@top.qwarx.com>  <20060831102459.fvnuefdw0848gscc@ebourne.me.uk>  <1D3961A7-0885-41CC-9F73-273CE2BB3AFA@fluffy.co.uk>  <Pine.LNX.4.64.0608311319030.11479@localhost>  <20060831134917.d3rpxireaskksgwc@ebourne.me.uk> <Pine.LNX.4.62.0608312043490.28816@top.qwarx.com> <1157065167.26754.16.camel@avenin.ebourne.me.uk> <Pine.LNX.4.62.0609010017450.28816@top.qwarx.com>
Message-ID: <DFA965B4-DBC6-4A66-8B2E-32C5B0100404@fluffy.co.uk>

On 1 Sep 2006, at 00:22, Chris Wilson wrote:

> Hi Martin,
>
> On Thu, 31 Aug 2006, Martin Ebourne wrote:
>
>> On Thu, 2006-08-31 at 20:51 +0100, Chris Wilson wrote:
>>> There is a possible clean solution, which is to use the Windows flag
>>> FILE_FLAG_DELETE_ON_CLOSE when opening the file. To avoid the  
>>> #ifdef, I
>>> think I would have to invent a new flag to pass to the FileStream
>>> constructor, which does nothing (define to zero) on other  
>>> platforms, but
>>> activates this Windows flag in openfile() on Win32.
>>>
>>> Does this seem like a sensible solution? And what should that  
>>> flag be
>>> called? O_DELETE_ON_CLOSE_WIN32?
>>
>> How about FileStream handling this platform specific stuff. All  
>> you need
>> to do is tell FileStream when you construct it (use an overloaded
>> constructor or give an extra bool parameter to the constructor)  
>> that you
>> want the file automatically deleted.
>
> Seems like a good idea, much cleaner. Will implement tomorrow.

I would prefer a class derived from FileStream, with a descriptive  
name. Perhaps InvisibleTempFileStream or something? Then it's very  
clear what's going on.


>
>> On windows it would then pass FILE_FLAG_DELETE_ON_CLOSE and on  
>> unix it
>> would unlink it after opening it.
>
> FileStream calls ::openfile (from lib/win32/emu.cpp) to open the  
> file, so I would have to pass a new flag anyway, or a new  
> parameter, to make this happen. Alternatibely, FileStream could  
> delete the file itself in the Close method.

It's nicer to get the OS to do it for us.

>
>> Does that cover all possibilities? If so it would certainly tidy  
>> up a lot of code around the place that is closing and deleting files.
>
> I'm not certain that it covers them all, particularly renaming over  
> open files, but I'm sure it would help.

Which bits in the code do this? This tends to be done for atomic  
updates, but there's no reason why the replaced file  needs to be  
open. Maybe just calling Close() on the file before the rename would  
do and be OK for cross platform too?

Ben





From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:09:52 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 09:09:52 +0100
Subject: [Box Backup-dev] COMMIT r862 - box/chris/merge/bin/bbstored
In-Reply-To: <E1GIspf-000C6W-9p@starbug.netinertia.co.uk>
References: <E1GIspf-000C6W-9p@starbug.netinertia.co.uk>
Message-ID: <B4438AE3-61A8-4A54-B281-730280503231@fluffy.co.uk>

Not nice. Either use exception handlers or that new class we  
discussed to make sure the unlink happens.

Ben


On 31 Aug 2006, at 21:08, subversion@fluffy.co.uk wrote:

> Author: chris
> Date: 2006-08-31 21:08:11 +0100 (Thu, 31 Aug 2006)
> New Revision: 862
>
> Modified:
>    box/chris/merge/bin/bbstored/BackupContext.cpp
> Log:
> * bin/bbstored/BackupContext.cpp
> - Delete the temporary file on Win32 just like on other platforms  
> (note
>   that this reduces the guarantees that the file will be deleted,
>   especially if an exception is thrown, refs #819)
>
>
> Modified: box/chris/merge/bin/bbstored/BackupContext.cpp
> ===================================================================
> --- box/chris/merge/bin/bbstored/BackupContext.cpp	2006-08-31  
> 19:59:02 UTC (rev 861)
> +++ box/chris/merge/bin/bbstored/BackupContext.cpp	2006-08-31  
> 20:08:11 UTC (rev 862)
> @@ -125,7 +125,6 @@
>  //  
> ---------------------------------------------------------------------- 
> ----
>  bool BackupContext::AttemptToGetWriteLock()
>  {
> -#ifndef WIN32
>  	// Make the filename of the write lock file
>  	std::string writeLockFile;
>  	StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet,  
> writeLockFile);
> @@ -159,10 +158,6 @@
>  	}
>  	
>  	return gotLock;
> -#else // WIN32
> -	// no housekeeping process, we do have the lock
> -	return true;
> -#endif // !WIN32
>  }
>
>
> @@ -466,12 +461,6 @@
>  #else
>  				FileStream diff(tempFn.c_str(), O_RDWR | O_CREAT | O_EXCL);
>  				FileStream diff2(tempFn.c_str(), O_RDONLY);
> -
> -				// Unlink it immediately, so it definitely goes away
> -				if(::unlink(tempFn.c_str()) != 0)
> -				{
> -					THROW_EXCEPTION(CommonException, OSFileError);
> -				}
>  #endif
>  				
>  				// Stream the incoming diff to this temporary file
> @@ -514,6 +503,14 @@
>  				spaceAdjustFromDiff = from->GetDiscUsageInBlocks() -  
> oldVersionNewBlocksUsed;
>
>  				// Everything cleans up here...
> +				diff.Close();
> +				diff2.Close();
> +
> +				// Unlink the temporary file
> +				if(::unlink(tempFn.c_str()) != 0)
> +				{
> +					THROW_EXCEPTION(CommonException, OSFileError);
> +				}
>  			}
>  			catch(...)
>  			{
>
> _______________________________________________
> Boxbackup-dev mailing list
> Boxbackup-dev@fluffy.co.uk
> http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:15:24 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 09:15:24 +0100
Subject: [Box Backup-dev] COMMIT r896 - box/chris/merge/lib/raidfile
In-Reply-To: <E1GIvMI-000GTi-CT@starbug.netinertia.co.uk>
References: <E1GIvMI-000GTi-CT@starbug.netinertia.co.uk>
Message-ID: <542496EF-7038-4C2C-A78D-3564564F00FC@fluffy.co.uk>

On 31 Aug 2006, at 23:50, subversion@fluffy.co.uk wrote:

> Author: chris
> Date: 2006-08-31 23:50:02 +0100 (Thu, 31 Aug 2006)
> New Revision: 896
>
> Modified:
>    box/chris/merge/lib/raidfile/RaidFileWrite.cpp
> Log:
> (refs #3)
>
> Open files in binary mode (Win32)

Can that go in the base classes instead? I can't think of any reason  
why we'd want to open a file in anything other than binary mode.


>
> Disable the lock failure block when we don't have any locking  
> mechanism

Win32 does have locks! Why not add an emulated flock() for Win32?

>
> Close and delete files before renaming over them on Win32. This breaks
> Ben's desired recovery semantics, so it's not done on other platforms,
> but Win32 requires it.

When something bad is done on Win32 which shouldn't be done on more  
capable platforms, please comment so noone goes "oh, that #ifdef is  
redundant, I'll remove it" or something.

Ben




>
>
> Modified: box/chris/merge/lib/raidfile/RaidFileWrite.cpp
> ===================================================================
> --- box/chris/merge/lib/raidfile/RaidFileWrite.cpp	2006-08-31  
> 22:44:18 UTC (rev 895)
> +++ box/chris/merge/lib/raidfile/RaidFileWrite.cpp	2006-08-31  
> 22:50:02 UTC (rev 896)
> @@ -104,7 +104,8 @@
>  	writeFilename += 'X';
>
>  	// Attempt to open
> -	mOSFileHandle = ::open(writeFilename.c_str(), O_WRONLY | O_CREAT,
> +	mOSFileHandle = ::open(writeFilename.c_str(),
> +		O_WRONLY | O_CREAT | O_BINARY,
>  		S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
>  	if(mOSFileHandle == -1)
>  	{
> @@ -115,7 +116,7 @@
>  #ifdef HAVE_FLOCK
>  	int errnoBlock = EWOULDBLOCK;
>  	if(::flock(mOSFileHandle, LOCK_EX | LOCK_NB) != 0)
> -#else
> +#elif HAVE_DECL_F_SETLK
>  	int errnoBlock = EAGAIN;
>  	struct flock desc;
>  	desc.l_type = F_WRLCK;
> @@ -123,6 +124,9 @@
>  	desc.l_start = 0;
>  	desc.l_len = 0;
>  	if(::fcntl(mOSFileHandle, F_SETLK, &desc) != 0)
> +#else
> +	int errnoBlock = ENOSYS;
> +	if (0)
>  #endif
>  	{
>  		// Lock was not obtained.
> @@ -242,23 +246,46 @@
>  	}
>  	
>  	// Rename it into place -- BEFORE it's closed so lock remains
> +
> +#ifdef WIN32
> +	// Except on Win32 which doesn't allow renaming open files
> +	// Close file...
> +	if(::close(mOSFileHandle) != 0)
> +	{
> +		THROW_EXCEPTION(RaidFileException, OSError)
> +	}
> +	mOSFileHandle = -1;
> +#endif // WIN32
> +
>  	RaidFileController &rcontroller(RaidFileController::GetController 
> ());
>  	RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber));
>  	// Get the filename for the write file
>  	std::string renameTo(RaidFileUtil::MakeWriteFileName(rdiscSet,  
> mFilename));
>  	// And the current name
>  	std::string renameFrom(renameTo + 'X');
> +
> +#ifdef WIN32
> +	// need to delete the target first
> +	if(::unlink(renameTo.c_str()) != 0 &&
> +		GetLastError() != ERROR_FILE_NOT_FOUND)
> +	{
> +		THROW_EXCEPTION(RaidFileException, OSError)
> +	}
> +#endif
> +
>  	if(::rename(renameFrom.c_str(), renameTo.c_str()) != 0)
>  	{
>  		THROW_EXCEPTION(RaidFileException, OSError)
>  	}
>  	
> +#ifndef WIN32	
>  	// Close file...
>  	if(::close(mOSFileHandle) != 0)
>  	{
>  		THROW_EXCEPTION(RaidFileException, OSError)
>  	}
>  	mOSFileHandle = -1;
> +#endif // !WIN32
>  	
>  	// Raid it?
>  	if(ConvertToRaidNow)
> @@ -292,8 +319,15 @@
>  	writeFilename += 'X';
>  	
>  	// Unlink and close it
> -	if((::unlink(writeFilename.c_str()) != 0)
> -		|| (::close(mOSFileHandle) != 0))
> +
> +#ifdef WIN32
> +	// On Win32 we must close it first
> +	if (::close(mOSFileHandle) != 0 ||
> +		::unlink(writeFilename.c_str()) != 0)
> +#else // !WIN32
> +	if (::unlink(writeFilename.c_str()) != 0 ||
> +		::close(mOSFileHandle) != 0)
> +#endif // !WIN32
>  	{
>  		THROW_EXCEPTION(RaidFileException, OSError)
>  	}
> @@ -388,13 +422,13 @@
>  	try
>  	{
>  #if HAVE_DECL_O_EXLOCK
> -		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK)> stripe1 
> (stripe1FilenameW.c_str());
> -		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK)> stripe2 
> (stripe2FilenameW.c_str());
> -		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK)> parity 
> (parityFilenameW.c_str());
> +		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK |  
> O_BINARY)> stripe1(stripe1FilenameW.c_str());
> +		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK |  
> O_BINARY)> stripe2(stripe2FilenameW.c_str());
> +		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK |  
> O_BINARY)> parity(parityFilenameW.c_str());
>  #else
> -		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL)> stripe1 
> (stripe1FilenameW.c_str());
> -		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL)> stripe2 
> (stripe2FilenameW.c_str());
> -		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL)> parity 
> (parityFilenameW.c_str());
> +		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe1 
> (stripe1FilenameW.c_str());
> +		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe2 
> (stripe2FilenameW.c_str());
> +		FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> parity 
> (parityFilenameW.c_str());
>  #endif
>
>  		// Then... read in data...
> @@ -530,6 +564,22 @@
>  		parity.Close();
>  		stripe2.Close();
>  		stripe1.Close();
> +
> +#ifdef WIN32
> +		// Must delete before renaming
> +		if (::unlink(stripe1Filename.c_str()) != 0 && errno != ENOENT)
> +		{
> +			THROW_EXCEPTION(RaidFileException, OSError);
> +		}
> +		if (::unlink(stripe2Filename.c_str()) != 0 && errno != ENOENT)
> +		{
> +			THROW_EXCEPTION(RaidFileException, OSError);
> +		}
> +		if (::unlink(parityFilename.c_str()) != 0 && errno != ENOENT)
> +		{
> +			THROW_EXCEPTION(RaidFileException, OSError);
> +		}
> +#endif
>  		
>  		// Rename them into place
>  		if(::rename(stripe1FilenameW.c_str(), stripe1Filename.c_str()) != 0
>
> _______________________________________________
> Boxbackup-dev mailing list
> Boxbackup-dev@fluffy.co.uk
> http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:16:38 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 09:16:38 +0100
Subject: [Box Backup-dev] COMMIT r892 - box/chris/merge/lib/common
In-Reply-To: <E1GIv8Z-000GCQ-Mv@starbug.netinertia.co.uk>
References: <E1GIv8Z-000GCQ-Mv@starbug.netinertia.co.uk>
Message-ID: <65DA87B9-D70D-4FF9-828E-6F0439F25B1E@fluffy.co.uk>

On 31 Aug 2006, at 23:35, subversion@fluffy.co.uk wrote:

> Author: chris
> Date: 2006-08-31 23:35:51 +0100 (Thu, 31 Aug 2006)
> New Revision: 892
>
> Modified:
>    box/chris/merge/lib/common/UnixUser.cpp
> Log:
> Disable all calls to set*id() on Win32 (doesn't work) (refs #3)

Wouldn't just returning 1 from those functions on Win32 be a better  
plan?

Use #defines to replace them in the Win32 header files with that  
modified function if necessary.

Ben




>
>
> Modified: box/chris/merge/lib/common/UnixUser.cpp
> ===================================================================
> --- box/chris/merge/lib/common/UnixUser.cpp	2006-08-31 22:33:05 UTC  
> (rev 891)
> +++ box/chris/merge/lib/common/UnixUser.cpp	2006-08-31 22:35:51 UTC  
> (rev 892)
> @@ -78,8 +78,12 @@
>  	if(mRevertOnDestruction)
>  	{
>  		// Revert to "real" user and group id of the process
> +		#ifdef WIN32
> +		if(0)
> +		#else
>  		if(::setegid(::getgid()) != 0
>  			|| ::seteuid(::getuid()) != 0)
> +		#endif
>  		{
>  			THROW_EXCEPTION(CommonException, CouldNotRestoreProcessUser)
>  		}
> @@ -101,8 +105,12 @@
>  	if(Temporary)
>  	{
>  		// Change temporarily (change effective only)
> +		#ifdef WIN32
> +		if(0)
> +		#else
>  		if(::setegid(mGID) != 0
>  			|| ::seteuid(mUID) != 0)
> +		#endif
>  		{
>  			THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser)
>  		}
> @@ -112,9 +120,13 @@
>  	}
>  	else
>  	{
> -		// Change perminantely (change all UIDs and GIDs)
> +		// Change permanently (change all UIDs and GIDs)
> +		#ifdef WIN32
> +		if(0)
> +		#else
>  		if(::setgid(mGID) != 0
>  			|| ::setuid(mUID) != 0)
> +		#endif
>  		{
>  			THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser)
>  		}
>
> _______________________________________________
> Boxbackup-dev mailing list
> Boxbackup-dev@fluffy.co.uk
> http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:36:15 2006
From: boxbackup-dev@fluffy.co.uk (Chris Wilson)
Date: Fri, 1 Sep 2006 09:36:15 +0100 (BST)
Subject: [Box Backup-dev] COMMIT r896 - box/chris/merge/lib/raidfile
In-Reply-To: <542496EF-7038-4C2C-A78D-3564564F00FC@fluffy.co.uk>
References: <E1GIvMI-000GTi-CT@starbug.netinertia.co.uk>
 <542496EF-7038-4C2C-A78D-3564564F00FC@fluffy.co.uk>
Message-ID: <Pine.LNX.4.62.0609010935450.28816@top.qwarx.com>

On Fri, 1 Sep 2006, Ben Summers wrote:

> Can that go in the base classes instead? I can't think of any reason why we'd 
> want to open a file in anything other than binary mode.
...
> Win32 does have locks! Why not add an emulated flock() for Win32?
...
> When something bad is done on Win32 which shouldn't be done on more capable 
> platforms, please comment so noone goes "oh, that #ifdef is redundant, I'll 
> remove it" or something.

OK, will do all.

Cheers, Chris.
-- 
_ ___ __     _
  / __/ / ,__(_)_  | Chris Wilson <0000 at qwirx.com> - Cambs UK |
/ (_/ ,\/ _/ /_ \ | Security/C/C++/Java/Perl/SQL/HTML Developer |
\ _/_/_/_//_/___/ | We are GNU-free your mind-and your software |


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:37:46 2006
From: boxbackup-dev@fluffy.co.uk (Chris Wilson)
Date: Fri, 1 Sep 2006 09:37:46 +0100 (BST)
Subject: [Box Backup-dev] COMMIT r892 - box/chris/merge/lib/common
In-Reply-To: <65DA87B9-D70D-4FF9-828E-6F0439F25B1E@fluffy.co.uk>
References: <E1GIv8Z-000GCQ-Mv@starbug.netinertia.co.uk>
 <65DA87B9-D70D-4FF9-828E-6F0439F25B1E@fluffy.co.uk>
Message-ID: <Pine.LNX.4.62.0609010936350.28816@top.qwarx.com>

Hi Ben,

On Fri, 1 Sep 2006, Ben Summers wrote:

> Wouldn't just returning 1 from those functions on Win32 be a better 
> plan?
>
> Use #defines to replace them in the Win32 header files with that 
> modified function if necessary.

I was trying to get away from using broken stubs that pretend to have done 
something useful, cf. getuid(), but I can do it that way if you prefer.

Cheers, Chris.
-- 
_ ___ __     _
  / __/ / ,__(_)_  | Chris Wilson <0000 at qwirx.com> - Cambs UK |
/ (_/ ,\/ _/ /_ \ | Security/C/C++/Java/Perl/SQL/HTML Developer |
\ _/_/_/_//_/___/ | We are GNU-free your mind-and your software |


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:45:50 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 09:45:50 +0100
Subject: [Box Backup-dev] COMMIT r922 - box/chris/merge/test/backupstore
Message-ID: <E1GJ4es-0006n3-FR@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 09:45:50 +0100 (Fri, 01 Sep 2006)
New Revision: 922

Modified:
   box/chris/merge/test/backupstore/testbackupstore.cpp
Log:
(refs #3)

Use correct directory separator everywhere


Modified: box/chris/merge/test/backupstore/testbackupstore.cpp
===================================================================
--- box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:08:11 UTC (rev 921)
+++ box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:45:50 UTC (rev 922)
@@ -948,7 +948,7 @@
 		// Set the client store marker
 		protocol.QuerySetClientStoreMarker(0x8732523ab23aLL);
 
-#ifndef WIN32
+#ifndef WIN32 // can't open more than one connection on Win32
 		// Open a new connection which is read only
 		SocketStreamTLS connReadOnly;
 		connReadOnly.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED);
@@ -968,6 +968,7 @@
 			TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL);
 		}
 #else // WIN32
+		// we can't open a new connection, so fake it
 		BackupProtocolClient& protocolReadOnly(protocol);
 #endif
 
@@ -1530,35 +1531,48 @@
 		
 		// The test block to a file
 		{
-			FileStream f("testfiles/testenc1", O_WRONLY | O_CREAT | O_EXCL);
+			FileStream f("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1", O_WRONLY | O_CREAT | O_EXCL);
 			f.Write(encfile, sizeof(encfile));
 		}
 		
 		// Encode it
 		{
-			FileStream out("testfiles/testenc1_enc", O_WRONLY | O_CREAT | O_EXCL);
-			BackupStoreFilenameClear name("testfiles/testenc1");
+			FileStream out("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1_enc", O_WRONLY | O_CREAT | O_EXCL);
+			BackupStoreFilenameClear name("testfiles"
+				DIRECTORY_SEPARATOR "testenc1");
 
-			std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testenc1", 32, name));
+			std::auto_ptr<IOStream> encoded(
+				BackupStoreFile::EncodeFile(
+					"testfiles" DIRECTORY_SEPARATOR
+					"testenc1", 32, name));
 			encoded->CopyStreamTo(out);
 		}
 		
 		// Verify it
 		{
-			FileStream enc("testfiles/testenc1_enc");
+			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1_enc");
 			TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc) == true);
 		}
 		
 		// Decode it
 		{
-			FileStream enc("testfiles/testenc1_enc");
-			BackupStoreFile::DecodeFile(enc, "testfiles/testenc1_orig", IOStream::TimeOutInfinite);
+			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1_enc");
+			BackupStoreFile::DecodeFile(enc, "testfiles"
+				DIRECTORY_SEPARATOR "testenc1_orig", 
+				IOStream::TimeOutInfinite);
 		}
 		
 		// Read in rebuilt original, and compare contents
 		{
-			TEST_THAT(TestGetFileSize("testfiles/testenc1_orig") == sizeof(encfile));
-			FileStream in("testfiles/testenc1_orig");
+			TEST_THAT(TestGetFileSize("testfiles" 
+				DIRECTORY_SEPARATOR "testenc1_orig") 
+				== sizeof(encfile));
+			FileStream in("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1_orig");
 			int encfile_i[ENCFILE_SIZE];
 			in.Read(encfile_i, sizeof(encfile_i));
 			TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0);
@@ -1566,7 +1580,8 @@
 		
 		// Check how many blocks it had, and test the stream based interface
 		{
-			FileStream enc("testfiles/testenc1_enc");
+			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1_enc");
 			std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(enc, IOStream::TimeOutInfinite));
 			CollectInBufferStream d;
 			decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */);
@@ -1580,10 +1595,15 @@
 		// Test that the last block in a file, if less than 256 bytes, gets put into the last block
 		{
 			#define FILE_SIZE_JUST_OVER	((4096*2)+58)
-			FileStream f("testfiles/testenc2", O_WRONLY | O_CREAT | O_EXCL);
+			FileStream f("testfiles" DIRECTORY_SEPARATOR 
+				"testenc2", O_WRONLY | O_CREAT | O_EXCL);
 			f.Write(encfile + 2, FILE_SIZE_JUST_OVER);
+			f.Close();
 			BackupStoreFilenameClear name("testenc2");
-			std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testenc2", 32, name));
+			std::auto_ptr<IOStream> encoded(
+				BackupStoreFile::EncodeFile(
+					"testfiles" DIRECTORY_SEPARATOR
+					"testenc2", 32, name));
 			CollectInBufferStream e;
 			encoded->CopyStreamTo(e);
 			e.SetForReading();
@@ -1599,7 +1619,8 @@
 		
 		// Test that reordered streams work too
 		{
-			FileStream enc("testfiles/testenc1_enc");
+			FileStream enc("testfiles" DIRECTORY_SEPARATOR 
+				"testenc1_enc");
 			std::auto_ptr<IOStream> reordered(BackupStoreFile::ReorderFileToStreamOrder(&enc, false));
 			std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(*reordered, IOStream::TimeOutInfinite));
 			CollectInBufferStream d;
@@ -1629,9 +1650,14 @@
 	// Store info
 	{
 		RaidFileWrite::CreateDirectory(0, "test-info");
-		BackupStoreInfo::CreateNew(76, "test-info/", 0, 3461231233455433LL, 2934852487LL);
-		TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, "test-info/", 0, 0, 0), RaidFileException, CannotOverwriteExistingFile);
-		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, true));
+		BackupStoreInfo::CreateNew(76, "test-info" DIRECTORY_SEPARATOR, 
+			0, 3461231233455433LL, 2934852487LL);
+		TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, 
+			"test-info" DIRECTORY_SEPARATOR, 0, 0, 0), 
+			RaidFileException, CannotOverwriteExistingFile);
+		std::auto_ptr<BackupStoreInfo> info(
+			BackupStoreInfo::Load(76, 
+				"test-info" DIRECTORY_SEPARATOR, 0, true));
 		TEST_CHECK_THROWS(info->Save(), BackupStoreException, StoreInfoIsReadOnly);
 		TEST_CHECK_THROWS(info->ChangeBlocksUsed(1), BackupStoreException, StoreInfoIsReadOnly);
 		TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(1), BackupStoreException, StoreInfoIsReadOnly);
@@ -1640,7 +1666,8 @@
 		TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly);
 	}
 	{
-		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, false));
+		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, 
+			"test-info" DIRECTORY_SEPARATOR, 0, false));
 		info->ChangeBlocksUsed(8);
 		info->ChangeBlocksInOldFiles(9);
 		info->ChangeBlocksInDeletedFiles(10);
@@ -1658,7 +1685,8 @@
 		info->Save();
 	}
 	{
-		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, true));
+		std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, 
+			"test-info" DIRECTORY_SEPARATOR, 0, true));
 		TEST_THAT(info->GetBlocksUsed() == 7);
 		TEST_THAT(info->GetBlocksInOldFiles() == 5);
 		TEST_THAT(info->GetBlocksInDeletedFiles() == 1);
@@ -1676,9 +1704,9 @@
 	// Context
 	TLSContext context;
 	context.Initialise(false /* client */,
-			"testfiles/clientCerts.pem",
-			"testfiles/clientPrivKey.pem",
-			"testfiles/clientTrustedCAs.pem");
+			"testfiles" DIRECTORY_SEPARATOR "clientCerts.pem",
+			"testfiles" DIRECTORY_SEPARATOR "clientPrivKey.pem",
+			"testfiles" DIRECTORY_SEPARATOR "clientTrustedCAs.pem");
 
 	// First, try logging in without an account having been created... just make sure login fails.
 	int pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid");


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:45:50 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:45:50 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.1146b67b5810f08bf81b793dc45a3304@fluffy.co.uk>

--===============1169040888==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


IFVOSVgu

--===============1169040888==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:47:48 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 09:47:48 +0100
Subject: [Box Backup-dev] COMMIT r923 - box/chris/merge/test/backupstore
Message-ID: <E1GJ4gm-000BdU-DB@starbug.netinertia.co.uk>

Author: chris
Date: 2006-09-01 09:47:48 +0100 (Fri, 01 Sep 2006)
New Revision: 923

Modified:
   box/chris/merge/test/backupstore/testbackupstore.cpp
Log:
(refs #3)

Disable symlink tests on Win32


Modified: box/chris/merge/test/backupstore/testbackupstore.cpp
===================================================================
--- box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:45:50 UTC (rev 922)
+++ box/chris/merge/test/backupstore/testbackupstore.cpp	2006-09-01 08:47:48 UTC (rev 923)
@@ -1632,6 +1632,7 @@
 			TEST_THAT(decoded->GetNumBlocks() == 3);
 		}
 		
+#ifndef WIN32 // no symlinks on Win32
 		// Try out doing this on a symlink
 		{
 			TEST_THAT(::symlink("does/not/exist", "testfiles/testsymlink") == 0);
@@ -1645,6 +1646,7 @@
 			// Decode it
 			BackupStoreFile::DecodeFile(b, "testfiles/testsymlink_2", IOStream::TimeOutInfinite);
 		}
+#endif
 	}
 
 	// Store info


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 09:47:48 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 08:47:48 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=233=3A_Merge_Win32_branch?=
In-Reply-To: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
References: <072.67cfd2e8543d09f0771fff956d24d898@fluffy.co.uk>
Message-ID: <081.d9fde42464896a6f2b6cb446a99a3509@fluffy.co.uk>

--===============0979446935==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============0979446935==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 10:34:38 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 10:34:38 +0100
Subject: [Box Backup-dev] COMMIT r892 - box/chris/merge/lib/common
In-Reply-To: <Pine.LNX.4.62.0609010936350.28816@top.qwarx.com>
References: <E1GIv8Z-000GCQ-Mv@starbug.netinertia.co.uk> <65DA87B9-D70D-4FF9-828E-6F0439F25B1E@fluffy.co.uk> <Pine.LNX.4.62.0609010936350.28816@top.qwarx.com>
Message-ID: <AF2AF038-F2AA-4F2B-8A1C-35025C7DD9B9@fluffy.co.uk>

On 1 Sep 2006, at 09:37, Chris Wilson wrote:

> Hi Ben,
>
> On Fri, 1 Sep 2006, Ben Summers wrote:
>
>> Wouldn't just returning 1 from those functions on Win32 be a  
>> better plan?
>>
>> Use #defines to replace them in the Win32 header files with that  
>> modified function if necessary.
>
> I was trying to get away from using broken stubs that pretend to  
> have done something useful, cf. getuid(), but I can do it that way  
> if you prefer.

I think it's preferable to lots of #ifdefs!

Ben





From boxbackup-dev@fluffy.co.uk  Fri Sep  1 10:42:46 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 10:42:46 +0100
Subject: [Box Backup-dev] COMMIT r924 - box
Message-ID: <E1GJ5Xy-000ENp-EJ@starbug.netinertia.co.uk>

Author: ben
Date: 2006-09-01 10:42:46 +0100 (Fri, 01 Sep 2006)
New Revision: 924

Added:
   box/features/
Log:
Features


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 10:42:50 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 10:42:50 +0100
Subject: [Box Backup-dev] COMMIT r925 - box/features
Message-ID: <E1GJ5Y2-0006sW-SK@starbug.netinertia.co.uk>

Author: ben
Date: 2006-09-01 10:42:50 +0100 (Fri, 01 Sep 2006)
New Revision: 925

Added:
   box/features/codeforintegration/
Log:
Create a place to upload a load of extra code, which may be useful.

Copied: box/features/codeforintegration (from rev 924, box/trunk)


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 10:48:04 2006
From: boxbackup-dev@fluffy.co.uk (Martin Ebourne)
Date: Fri, 01 Sep 2006 10:48:04 +0100
Subject: [Box Backup-dev] COMMIT r925 - box/features
In-Reply-To: <E1GJ5Y2-0006sW-SK@starbug.netinertia.co.uk>
References: <E1GJ5Y2-0006sW-SK@starbug.netinertia.co.uk>
Message-ID: <1157104084.30210.1.camel@avenin.ebourne.me.uk>

On Fri, 2006-09-01 at 10:42 +0100, subversion@fluffy.co.uk wrote:
> Author: ben
> Date: 2006-09-01 10:42:50 +0100 (Fri, 01 Sep 2006)
> New Revision: 925
> 
> Added:
>    box/features/codeforintegration/
> Log:
> Create a place to upload a load of extra code, which may be useful.
> 
> Copied: box/features/codeforintegration (from rev 924, box/trunk)

Do you think it's worth creating a trac task ticket and then refs #n
each of the commits? I think it helps tie everything back to one place
very easily, better than trying to trace back through branches and
merges etc.

Cheers,

Martin.


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 11:15:36 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 10:15:36 -0000
Subject: [Box Backup-dev] =?utf-8?q?=236=3A_Contribute_code=3A_SMTP_client=2C_HTTP_server=2C_Databa?=
 =?utf-8?q?se_drivers=2C_Web_app_framework?=
Message-ID: <072.ab75057719ec9ae573455ab70d09b933@fluffy.co.uk>

--===============0832263486==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64


b21hdGljIG9uLWxpbmUgYmFja3VwIHN5c3RlbSBmb3IgVU5JWC4=

--===============0832263486==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 11:20:46 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 11:20:46 +0100
Subject: [Box Backup-dev] COMMIT r926 - in box/features/codeforintegration: . TEMP_for_autoconfification distribution distribution/boxwaf docs docs/database docs/database/lib_database docs/webappframework docs/webappframework/web_application lib lib/database lib/database/Database lib/dbdriver lib/dbdrv_mysql lib/dbdrv_postgresql lib/dbdrv_sqlite lib/httpserver lib/perl lib/smtpclient lib/webappframework lib/webappframework/WebAppFramework lib/webappframework/WebAppFramework/Locale lib/webappframework/WebAppFramework/Unit lib/webappframework/WebAppFramework/Unit/DataSource lib/webappframework/WebAppFramework/Unit/Database lib/webappframework/WebAppFramework/Unit/FormItem lib/webappframework/WebAppFramework/Unit/Localised lib/webappframework/WebAppFramework/Validator test test/database test/httpserver test/httpserver/testfiles test/smtpclient test/webappframework test/webappframework/Pages test/webappframework/Templates test/webappframework/testfiles
Message-ID: <E1GJ68k-000GPh-CV@starbug.netinertia.co.uk>

Author: ben
Date: 2006-09-01 11:20:43 +0100 (Fri, 01 Sep 2006)
New Revision: 926

Added:
   box/features/codeforintegration/TEMP_for_autoconfification/
   box/features/codeforintegration/TEMP_for_autoconfification/database_tests.pl
   box/features/codeforintegration/distribution/boxwaf/
   box/features/codeforintegration/distribution/boxwaf/CONTACT.txt
   box/features/codeforintegration/distribution/boxwaf/DISTRIBUTION-MANIFEST.txt
   box/features/codeforintegration/distribution/boxwaf/DOCUMENTATION.txt
   box/features/codeforintegration/distribution/boxwaf/LICENSE.txt
   box/features/codeforintegration/distribution/boxwaf/VERSION.txt
   box/features/codeforintegration/docs/database/
   box/features/codeforintegration/docs/database/lib_database.txt
   box/features/codeforintegration/docs/database/lib_database/
   box/features/codeforintegration/docs/database/lib_database/AutoIncrement.txt
   box/features/codeforintegration/docs/database/lib_database/AutogenQuery.txt
   box/features/codeforintegration/docs/database/lib_database/Vendorisation.txt
   box/features/codeforintegration/docs/webappframework/
   box/features/codeforintegration/docs/webappframework/web_application.txt
   box/features/codeforintegration/docs/webappframework/web_application/
   box/features/codeforintegration/docs/webappframework/web_application/Code.txt
   box/features/codeforintegration/docs/webappframework/web_application/DataSource.txt
   box/features/codeforintegration/docs/webappframework/web_application/Database.txt
   box/features/codeforintegration/docs/webappframework/web_application/DatabaseAuthenticate.txt
   box/features/codeforintegration/docs/webappframework/web_application/DatabaseDisplay.txt
   box/features/codeforintegration/docs/webappframework/web_application/DatabaseNewOrEdit.txt
   box/features/codeforintegration/docs/webappframework/web_application/Example.txt
   box/features/codeforintegration/docs/webappframework/web_application/FixedPoint.txt
   box/features/codeforintegration/docs/webappframework/web_application/FormContainers.txt
   box/features/codeforintegration/docs/webappframework/web_application/FormItems.txt
   box/features/codeforintegration/docs/webappframework/web_application/Forms.txt
   box/features/codeforintegration/docs/webappframework/web_application/GeneratedHandlers.txt
   box/features/codeforintegration/docs/webappframework/web_application/GettingStarted.txt
   box/features/codeforintegration/docs/webappframework/web_application/ImplementingTemplates.txt
   box/features/codeforintegration/docs/webappframework/web_application/Locale.txt
   box/features/codeforintegration/docs/webappframework/web_application/Localised.txt
   box/features/codeforintegration/docs/webappframework/web_application/NotesForTranslators.txt
   box/features/codeforintegration/docs/webappframework/web_application/OtherUnits.txt
   box/features/codeforintegration/docs/webappframework/web_application/StaticFiles.txt
   box/features/codeforintegration/docs/webappframework/web_application/TemplateUnits.txt
   box/features/codeforintegration/docs/webappframework/web_application/Translations.txt
   box/features/codeforintegration/docs/webappframework/web_application/Units.txt
   box/features/codeforintegration/docs/webappframework/web_application/Validators.txt
   box/features/codeforintegration/docs/webappframework/web_application/WebAppStructure.txt
   box/features/codeforintegration/docs/webappframework/web_application/WritingFormItems.txt
   box/features/codeforintegration/lib/database/
   box/features/codeforintegration/lib/database/Database/
   box/features/codeforintegration/lib/database/Database/Query.pm
   box/features/codeforintegration/lib/database/DatabaseConnection.cpp
   box/features/codeforintegration/lib/database/DatabaseConnection.h
   box/features/codeforintegration/lib/database/DatabaseDriverRegistration.cpp
   box/features/codeforintegration/lib/database/DatabaseDriverRegistration.h
   box/features/codeforintegration/lib/database/DatabaseQuery.cpp
   box/features/codeforintegration/lib/database/DatabaseQuery.h
   box/features/codeforintegration/lib/database/DatabaseQueryGeneric.cpp
   box/features/codeforintegration/lib/database/DatabaseQueryGeneric.h
   box/features/codeforintegration/lib/database/makedbcreate.pl
   box/features/codeforintegration/lib/database/makedbmake.pl
   box/features/codeforintegration/lib/database/makequeries.pl
   box/features/codeforintegration/lib/dbdriver/
   box/features/codeforintegration/lib/dbdriver/DatabaseDriver.cpp
   box/features/codeforintegration/lib/dbdriver/DatabaseDriver.h
   box/features/codeforintegration/lib/dbdriver/DatabaseException.txt
   box/features/codeforintegration/lib/dbdriver/DatabaseTypes.h
   box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.cpp
   box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.h
   box/features/codeforintegration/lib/dbdriver/Makefile.extra
   box/features/codeforintegration/lib/dbdrv_mysql/
   box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.cpp
   box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.h
   box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.cpp
   box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.h
   box/features/codeforintegration/lib/dbdrv_postgresql/
   box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.cpp
   box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.h
   box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.cpp
   box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.h
   box/features/codeforintegration/lib/dbdrv_postgresql/PostgreSQLOidTypes.h
   box/features/codeforintegration/lib/dbdrv_postgresql/getpostgresqloidtypes.pl
   box/features/codeforintegration/lib/dbdrv_sqlite/
   box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.cpp
   box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.h
   box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqliteV3.h
   box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.cpp
   box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.h
   box/features/codeforintegration/lib/httpserver/
   box/features/codeforintegration/lib/httpserver/HTTPException.txt
   box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.cpp
   box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.h
   box/features/codeforintegration/lib/httpserver/HTTPRequest.cpp
   box/features/codeforintegration/lib/httpserver/HTTPRequest.h
   box/features/codeforintegration/lib/httpserver/HTTPResponse.cpp
   box/features/codeforintegration/lib/httpserver/HTTPResponse.h
   box/features/codeforintegration/lib/httpserver/HTTPServer.cpp
   box/features/codeforintegration/lib/httpserver/HTTPServer.h
   box/features/codeforintegration/lib/httpserver/Makefile.extra
   box/features/codeforintegration/lib/perl/
   box/features/codeforintegration/lib/perl/CppDataClass.pm
   box/features/codeforintegration/lib/perl/CppVariable.pm
   box/features/codeforintegration/lib/smtpclient/
   box/features/codeforintegration/lib/smtpclient/Makefile.extra
   box/features/codeforintegration/lib/smtpclient/SMTPClient.cpp
   box/features/codeforintegration/lib/smtpclient/SMTPClient.h
   box/features/codeforintegration/lib/smtpclient/SMTPClientException.txt
   box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.cpp
   box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.h
   box/features/codeforintegration/lib/webappframework/
   box/features/codeforintegration/lib/webappframework/Makefile.extra
   box/features/codeforintegration/lib/webappframework/StarterTemplate.txt
   box/features/codeforintegration/lib/webappframework/WAFFixedPoint.cpp
   box/features/codeforintegration/lib/webappframework/WAFFormItemDate.cpp
   box/features/codeforintegration/lib/webappframework/WAFFormItemDate.h
   box/features/codeforintegration/lib/webappframework/WAFLocale.cpp
   box/features/codeforintegration/lib/webappframework/WAFLocale.h
   box/features/codeforintegration/lib/webappframework/WAFLocale_en.cpp
   box/features/codeforintegration/lib/webappframework/WAFLocale_en.h
   box/features/codeforintegration/lib/webappframework/WAFLocale_it.cpp
   box/features/codeforintegration/lib/webappframework/WAFLocale_it.h
   box/features/codeforintegration/lib/webappframework/WAFLocale_jp.cpp
   box/features/codeforintegration/lib/webappframework/WAFLocale_jp.h
   box/features/codeforintegration/lib/webappframework/WAFLocale_nl.cpp
   box/features/codeforintegration/lib/webappframework/WAFLocale_nl.h
   box/features/codeforintegration/lib/webappframework/WAFNumberField.cpp
   box/features/codeforintegration/lib/webappframework/WAFPhoneNumber.cpp
   box/features/codeforintegration/lib/webappframework/WAFUKPostcode.cpp
   box/features/codeforintegration/lib/webappframework/WAFUKPostcode.h
   box/features/codeforintegration/lib/webappframework/WAFUtilityFns.h
   box/features/codeforintegration/lib/webappframework/WebAppForm.cpp
   box/features/codeforintegration/lib/webappframework/WebAppForm.h
   box/features/codeforintegration/lib/webappframework/WebAppFramework.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/ArgumentAdaptor.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/DateAndTime.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/FixedPoint.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/LanguageFile.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/en.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/it.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/jp.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/nl.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Output.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookup.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookupList.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Code.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/CppSTLContainer.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/DatabaseQuery.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/Null.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/StaticStrings.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Authenticate.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/DisplayQuery.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/ExecuteQuery.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/FormNewOrEdit.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/OnSubmitExecSQL.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/QueryObject.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Table.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FixedPointNumber.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Form.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormErrorDisplay.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Checkbox.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Choice.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Date.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/ExtraDataMember.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/NumberField.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/SubmitButton.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/TextField.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormTableContainer.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FragmentsTemplate.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/IncludeOnPages.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/LinkToPage.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ListOfLinks.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/Date.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/DateTime.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Menu.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/OutputIf.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/PageTemplate.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/RawHTML.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SectionTemplate.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SimpleContainer.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TableContainer.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Templated.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TemplatedInsert.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TranslatedText.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Variable.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/email.pm
   box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/phone.pm
   box/features/codeforintegration/lib/webappframework/WebAppFrameworkException.txt
   box/features/codeforintegration/lib/webappframework/WebApplication.cpp
   box/features/codeforintegration/lib/webappframework/WebApplication.h
   box/features/codeforintegration/lib/webappframework/WebApplication.pl
   box/features/codeforintegration/lib/webappframework/WebApplicationObject.cpp
   box/features/codeforintegration/lib/webappframework/WebApplicationObject.h
   box/features/codeforintegration/lib/webappframework/genstarterapp.pl
   box/features/codeforintegration/lib/webappframework/preprocesslangfortrans.pl
   box/features/codeforintegration/lib/webappframework/processlangfromtrans.pl
   box/features/codeforintegration/test/database/
   box/features/codeforintegration/test/database/Makefile.extra
   box/features/codeforintegration/test/database/testdatabase.cpp
   box/features/codeforintegration/test/database/testdb.schema
   box/features/codeforintegration/test/database/testextra
   box/features/codeforintegration/test/database/testfiles/
   box/features/codeforintegration/test/database/testqueries.query
   box/features/codeforintegration/test/httpserver/
   box/features/codeforintegration/test/httpserver/testfiles/
   box/features/codeforintegration/test/httpserver/testfiles/httpserver.conf
   box/features/codeforintegration/test/httpserver/testfiles/testrequests.pl
   box/features/codeforintegration/test/httpserver/testhttpserver.cpp
   box/features/codeforintegration/test/smtpclient/
   box/features/codeforintegration/test/smtpclient/testsmtpclient.cpp
   box/features/codeforintegration/test/webappframework/
   box/features/codeforintegration/test/webappframework/Languages/
   box/features/codeforintegration/test/webappframework/Makefile.extra
   box/features/codeforintegration/test/webappframework/Pages/
   box/features/codeforintegration/test/webappframework/Pages/DeleteEntry.pl
   box/features/codeforintegration/test/webappframework/Pages/DisplayEntry.pl
   box/features/codeforintegration/test/webappframework/Pages/ListEntries.pl
   box/features/codeforintegration/test/webappframework/Pages/Login.pl
   box/features/codeforintegration/test/webappframework/Pages/Logout.pl
   box/features/codeforintegration/test/webappframework/Pages/Main.pl
   box/features/codeforintegration/test/webappframework/Pages/NewEntry.pl
   box/features/codeforintegration/test/webappframework/Templates/
   box/features/codeforintegration/test/webappframework/Templates/TestWebAppMain.en.html
   box/features/codeforintegration/test/webappframework/TestWebApp.cpp
   box/features/codeforintegration/test/webappframework/TestWebApp.h
   box/features/codeforintegration/test/webappframework/TestWebApp.pl
   box/features/codeforintegration/test/webappframework/TestWebApp.schema
   box/features/codeforintegration/test/webappframework/TestWebAppCode.cpp
   box/features/codeforintegration/test/webappframework/makeCAPStranslation.pl
   box/features/codeforintegration/test/webappframework/testfiles/
   box/features/codeforintegration/test/webappframework/testfiles/testwebapp.conf
   box/features/codeforintegration/test/webappframework/testwebappframework.cpp
Modified:
   box/features/codeforintegration/modules.txt
Log:
Contribute code: SMTP client, HTTP server, Database drivers, Web app framework,   refs #6

Added: box/features/codeforintegration/TEMP_for_autoconfification/database_tests.pl
===================================================================
--- box/features/codeforintegration/TEMP_for_autoconfification/database_tests.pl	                        (rev 0)
+++ box/features/codeforintegration/TEMP_for_autoconfification/database_tests.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,115 @@
+
+# perl fragment, not directly runnable
+
+{
+	# test for sqlite3
+	my $sqlite3_available = do_test('Name' => 'SQLite3',
+		'TestCompileFlags' => '-lsqlite3 ',
+		'SuccessCompileFlags' => '-DPLATFORM_SQLITE3',
+		'SuccessFlags' => ['LIBTRANS_-lsqlite=>-lsqlite3'],
+		'Code' => <<__E);
+#include "sqlite3.h"
+int main(int argc, char *argv[])
+{
+        ::sqlite3_open(0,0);
+        ::sqlite3_get_table(0,0,0,0,0,0);
+        ::sqlite3_mprintf(0);
+        ::sqlite3_free(0);
+        ::sqlite3_last_insert_rowid(0);
+        ::sqlite3_close(0);
+        return 0;
+}
+__E
+	unless($sqlite3_available)
+	{
+		# test for sqlite v2 if sqlite3 isn't present
+		do_test('Name' => 'SQLite',
+			'TestCompileFlags' => '-lsqlite ',
+			'FailureText' => "\n*** SQLite database driver disabled\n\n",
+			'FailureFlags' => ['IGNORE_lib/dbdrv_sqlite'],
+			'Code' => <<__E);
+#include "sqlite.h"
+int main(int argc, char *argv[])
+{
+	::sqlite_open(0,0,0);
+	::sqlite_get_table(0,0,0,0,0,0);
+	::sqlite_mprintf(0);
+	::sqlite_freemem(0);
+	::sqlite_last_insert_rowid(0);
+	::sqlite_close(0);
+	return 0;
+}
+__E
+	}
+
+	# test for MySQL
+	do_test('Name' => 'MySQL',
+		'TestCompileFlags' => '-lmysqlclient ',
+		'FailureText' => "\n*** MySQL database driver disabled\n\n",
+		'FailureFlags' => ['IGNORE_lib/dbdrv_mysql'],
+		'Code' => <<__E);
+#include "mysql/mysql.h"
+int main(int argc, char *argv[])
+{
+	::mysql_options(0,MYSQL_OPT_CONNECT_TIMEOUT,0);
+	::mysql_init(0);
+	::mysql_real_connect(0,0,0,0,0,0,0,0);
+	::mysql_close(0);
+	::mysql_real_escape_string(0,0,0,0);
+	::mysql_real_query(0,0,0);
+	::mysql_store_result(0);
+	::mysql_affected_rows(0);
+	::mysql_num_fields(0);
+	::mysql_num_rows(0);
+	::mysql_fetch_row(0);
+	::mysql_insert_id(0);
+	return 0;
+}
+__E
+
+	# test for PostgreSQL
+	my $pg_available = do_test('Name' => 'PostgreSQL',
+		'TestCompileFlags' => '-lpq ',
+		'FailureText' => "\n*** PostgreSQL database driver disabled\n\n",
+		'FailureFlags' => ['IGNORE_lib/dbdrv_postgresql'],
+		'Code' => <<__E);
+#include "postgresql/libpq-fe.h"
+int main(int argc, char *argv[])
+{
+	PGconn *pconnection = ::PQconnectdb("");
+	bool z = (::PQstatus(pconnection) != CONNECTION_OK);
+	::PQfinish(pconnection);
+	size_t len = ::PQescapeString(NULL, "", 0);
+	PGresult *presults = ::PQexec(pconnection, "sql");
+	::PQresultStatus(presults);
+	::PQclear(presults);
+	return 0;
+}
+__E
+	# if PostgreSQL is available, see if the new API stuff is there
+	if($pg_available)
+	{
+		do_test('Name' => 'PostgreSQL7.4+',
+			'TestCompileFlags' => '-lpq -lpgtypes ',
+			'FailureFlags' => ['LIBTRANS_-lpgtypes=>'],
+			'FailureCompileFlags' => '-DPLATFORM_POSTGRESQL_OLD_API',
+			'Code' => <<__E);
+#include "postgresql/libpq-fe.h"
+extern "C" {
+#include "postgresql/pgtypes_numeric.h"
+}
+int main(int argc, char *argv[])
+{
+	PGconn *pconnection = ::PQconnectdb("");
+	PGresult *presults = ::PQexecParams(pconnection, "sql", 0, NULL, NULL, NULL, NULL, 1);
+	::PQfformat(presults, 0);
+	::PQftype(presults, 0);
+	::PGTYPESnumeric_to_long(0,0);
+	return 0;
+}
+__E
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/distribution/boxwaf/CONTACT.txt
===================================================================
--- box/features/codeforintegration/distribution/boxwaf/CONTACT.txt	                        (rev 0)
+++ box/features/codeforintegration/distribution/boxwaf/CONTACT.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,4 @@
+
+Ben Summers
+ben@fluffy.co.uk
+

Added: box/features/codeforintegration/distribution/boxwaf/DISTRIBUTION-MANIFEST.txt
===================================================================
--- box/features/codeforintegration/distribution/boxwaf/DISTRIBUTION-MANIFEST.txt	                        (rev 0)
+++ box/features/codeforintegration/distribution/boxwaf/DISTRIBUTION-MANIFEST.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,34 @@
+MKDIR bin
+lib/dbdriver
+lib/dbdrv_sqlite
+lib/dbdrv_mysql
+lib/dbdrv_postgresql
+lib/database
+lib/database/Database
+lib/perl
+test/database
+lib/httpserver
+test/httpserver
+test/httpserver/testfiles
+lib/smtpclient
+test/smtpclient
+lib/webappframework
+lib/webappframework/WebAppFramework
+lib/webappframework/WebAppFramework/Unit
+lib/webappframework/WebAppFramework/Unit/Database
+lib/webappframework/WebAppFramework/Unit/DataSource
+lib/webappframework/WebAppFramework/Unit/FormItem
+lib/webappframework/WebAppFramework/Unit/Localised
+lib/webappframework/WebAppFramework/Locale
+lib/webappframework/WebAppFramework/Validator
+test/webappframework
+test/webappframework/testfiles
+test/webappframework/Languages
+test/webappframework/Pages
+test/webappframework/Templates
+docs/database notes
+docs/database/lib_database notes/lib_database
+docs/webappframework notes
+docs/webappframework/web_application notes/web_application
+infrastructure/tests/database_tests.pl
+infrastructure/setupexternal.pl

Added: box/features/codeforintegration/distribution/boxwaf/DOCUMENTATION.txt
===================================================================
--- box/features/codeforintegration/distribution/boxwaf/DOCUMENTATION.txt	                        (rev 0)
+++ box/features/codeforintegration/distribution/boxwaf/DOCUMENTATION.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,5 @@
+
+See the notes/ directory for documentation.
+
+Start with notes/web_application/GettingStarted.txt
+

Added: box/features/codeforintegration/distribution/boxwaf/LICENSE.txt
===================================================================
--- box/features/codeforintegration/distribution/boxwaf/LICENSE.txt	                        (rev 0)
+++ box/features/codeforintegration/distribution/boxwaf/LICENSE.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,36 @@
+ 
+Copyright (c) 2003 - 2006
+     Ben Summers and contributors.  All rights reserved.
+ 
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. All use of this software and associated advertising materials must 
+   display the following acknowledgment:
+       This product includes software developed by Ben Summers.
+4. The names of the Authors may not be used to endorse or promote
+   products derived from this software without specific prior written
+   permission.
+
+[Where legally impermissible the Authors do not disclaim liability for 
+direct physical injury or death caused solely by defects in the software 
+unless it is modified by a third party.]
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+ 
+ 

Added: box/features/codeforintegration/distribution/boxwaf/VERSION.txt
===================================================================
--- box/features/codeforintegration/distribution/boxwaf/VERSION.txt	                        (rev 0)
+++ box/features/codeforintegration/distribution/boxwaf/VERSION.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,2 @@
+0.01
+boxwaf

Added: box/features/codeforintegration/docs/database/lib_database/AutoIncrement.txt
===================================================================
--- box/features/codeforintegration/docs/database/lib_database/AutoIncrement.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/database/lib_database/AutoIncrement.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,19 @@
+TITLE Auto-incrementing columns
+
+All database servers have a way of generating unique IDs for inserted rows by defining a column to have a special type. If a value for this column is not specified in the SQL INSERT statement, then the database server sets the value itself. This value is usually one more than the last value automatically inserted, although it may be MAX(colname)+1. The value can then be retrieved by another SQL statement or an API call -- effectively an atomic operation so multiple processes can use this at once.
+
+The drivers abstract this behaviour out, and the autogenerated query objects make the retrieval of this value cleaner.
+
+Each server has it's own way of specifying these columns, so the column type is abstracted by using the `AUTO_INCREMENT_INT column type. The column type will be
+
+* Indexed
+* Marked as the PRIMARY KEY
+* Must be UNIQUE
+* Only used once in a table
+
+Because most servers only allow the retrieval of the last inserted value in the session, this is necessarily an operation on the DatabaseConnection object, with the GetLastAutoIncrementValue() method returning the integer. Since some servers require the table and column name, this must be passed in.
+
+Autogenerated query objects which use the AutoIncrementValue attribute have a InsertedValue() function which takes no arguments, and can be used at any time after the query has been executed, even if another one is executed in the meantime. (This works by simply calling GetLastAutoIncrementValue() after the query has executed, and recording the value.)
+
+See test/database for examples.
+

Added: box/features/codeforintegration/docs/database/lib_database/AutogenQuery.txt
===================================================================
--- box/features/codeforintegration/docs/database/lib_database/AutogenQuery.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/database/lib_database/AutogenQuery.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,120 @@
+TITLE Autogenerated query objects
+
+Perl scripts write query objects for even neater database queries. SQL statments and their use can be separated from the code into separate files, and/or included inline in the cpp files.
+
+
+SUBTITLE Schema files
+
+Schema files contain all the SQL commands necessary to set up a database -- not just CREATE TABLE commands. Each command must be ; terminated. Comments are started by # and continue to the end of the file.
+
+makedbcreate.pl will then generate two functions -- a create function which executes all these SQL queries, and a drop function which DROPs all the tables created.
+
+makedbcreate.pl input.schema output.cpp output.h
+
+The functions will be called input_Create(db) and input_Drop(db) (where input.schema is the name of the schema file).
+
+It is recommended that the makedbmake.pl script is used to generate the makefile which will run this, see below.
+
+
+SUBTITLE Query definition
+
+Queries look like this
+
+SQL Query
+[
+	Name: Test2
+	Statement: SELECT fInteger,fString FROM tTest1 WHERE fInteger=$1 AND fString=$2
+	Parameters: int, std::string StringParam NULL?
+	Results: int Integer, std::string String
+]
+
+and may appear anywhere in a .query or .cpp file. The lines are of the form "Attribute: Value", where Value may extend over multiple lines.
+
+Name - specifies the name of the C++ class generated.
+
+Statement: The SQL statement, with $n insertation markers.
+
+Parameters: Types and optional names of the parameters (corresponding to insertation markers)
+
+Results: Types and names of the results.
+
+Flags: Options. Only one at the moment is SingleValue.
+
+AutoIncrementValue: (see below)
+
+
+If the type and name of a result or parameter is followed by the string NULL?, then this means that item might be null. For parameters, this means the parameter is passed in by a pointer (so that it can be NULL), and for results, means a Is<Name>Null() function is generated.
+
+Types should be std::string, int or int32_t.
+
+If the SingleValue option is set, then an additional static Do() function is generated, which given a database connection and the parameters, executes the query and returns the single value. Note the Results still must be set, to obtain the type of the result value.
+
+
+SUBTITLE Runtime statments
+
+If the statement is the string "runtime", then the generated class will be derived off DatabaseQueryGeneric, and the statement can be specified at runtime.
+
+This is useful for when exact WHERE clauses are not known until runtime, but the results from the query are known.
+
+
+SUBTITLE Query objects for other code generation systems
+
+Other code generation systems (for example, the web application framework) may use batabase query objects. To create one, do
+
+my $query = Database::Query->new('Name' => 'Test2', 'Statement' => 'SQL', ...);
+
+where the parameters are the attributes as in the query definition above.
+
+
+SUBTITLE Auto-incrementing fields
+
+If the
+
+	AutoIncrementValue: TableName ColumnName
+
+attribute is set, then the autogenerated query will have an InsertedID() method, which will return the auto-incremented value (see AutoIncrement.txt). In addition, a Do() method will be generated, which will return this value.
+
+
+SUBTITLE Using an autogenerated query
+
+The constructor of the autogenerated class just takes a reference to the DatabaseConnection object. The Execute() function takes typed parameters as described in the "Parameters" attribute. Other than that, it's used exactly as any other query object, except that field values are obtained by Get<Name>() functions, rather than generic field functions referencing the column number.
+
+The benefits over DatabaseQueryGeneric are:
+
+1) Named query, with SQL moved away from the actual code to the definition of the query.
+
+2) It is possible to completely separate the database storage and the SQL from C++ code.
+
+3) One-shot "single result value" queries have a static one-line Do() function.
+
+4) Parameters to Execute and Do() are strongly typed, and when multiple parameters are used, less clumsy to specify with the type string.
+
+5) Names of the fields are used instead of generic field retrieval functions with column numbers.
+
+6) Auto-incrementing fields are handled in a neater way.
+
+
+SUBTITLE Setting up
+
+Include the following lines in the Makefile.extra file:
+
+========================================================================
+# AUTOGEN SEEDING
+autogen_db/testdb_schema.cpp:	testdb.schema
+	../../lib/database/makedbmake.pl .
+
+# include-makefile: Makefile.db
+========================================================================
+
+(assuming you have a schema file, otherwise, use a query file)
+
+This will setup a makefile which will
+
+1) For every .query file in the directory, autogenerate queries from it in the simiarly named files within autogen_db.
+
+2) For every .cpp file which contains SQL Query sections, generate files withing autogen_db called name_query.h/cpp where the file is called 'name.cpp'.
+
+So, to use the queries within name.queries, include "autogen_db/name.h" in your cpp file, where the query file is called "name.query'.
+
+To use statements included in your cpp file, include "autogen_db/name_query.h" in your cpp file, where that cpp file is called name.cpp.
+

Added: box/features/codeforintegration/docs/database/lib_database/Vendorisation.txt
===================================================================
--- box/features/codeforintegration/docs/database/lib_database/Vendorisation.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/database/lib_database/Vendorisation.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,46 @@
+TITLE Database SQL Statement vendorisation
+
+Each database server does some slightly non-standard things differently. In an attempt to allow a single SQL statement to work with multiple databases, even when using some of these "advanced" features, SQL statements are optionally "vendorised" before being passed to the database drivers.
+
+This is a simple system of search and replace (with optional arguments) for strings within the statment, called generic representations. All strings begin with a ` character.
+
+A generic respresentation is of the form
+
+	`NAME
+
+where NAME is a defined name composed of A-Z and _ only. It may optionally be given arguments, as
+
+	`NAME(a,b,c)
+
+where a, b, and c are arbitary strings which are used within the vendor specific representation of the generic representation.
+
+
+Note that vendorisation is an optional step, for efficiency. Autogenerated queries detect whether it is necessary, but queries using DatabaseQueryGeneric must set a flag in the constructor if it's necessary.
+
+
+SUBTITLE Supported generic representations
+
+All database drivers support the following:
+
+* AUTO_INCREMENT_INT
+
+The column type for a column which is automatically generated by the database as an incrementing 4 byte integer -- useful for allocating objects to new IDs. See AutoIncrement.txt for more details of how to use this.
+
+* LIMIT(offset,number)
+
+Limit the number of results returned.
+
+* CREATE_INDEX_CASE_INSENSTIVE(name, table, column)
+
+Create an index of the column converted to lowercase, for use with CASE_INSENSITIVE_COLUMN(). Create a normal index if the database does not support this.
+
+* CASE_INSENSITIVE_COLUMN(columnname)
+
+For use with CREATE_INDEX_CASE_INSENSTIVE -- whenever you want to use the column, use this macro. And make sure the value you compare it to is all lower case.
+
+* COLUMN_CASE_INSENSITIVE_ORDERING
+
+When used in a type specifier for a column, mark the column as case insensitively ordered. Will be ignored if the database does not support it.
+
+
+

Added: box/features/codeforintegration/docs/database/lib_database.txt
===================================================================
--- box/features/codeforintegration/docs/database/lib_database.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/database/lib_database.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,103 @@
+TITLE lib/database
+
+Provides a generic interface to a number of database servers.
+
+Design aims:
+
+* totally consitent across databases: abstract away differences in API and SQL
+
+* efficient
+
+* easy to use
+	* parameters in query strings
+	* repeated queries with different values
+	* creation and destruction of backend objects happens automatically.
+
+* autogeneration of code, as well as simple in-line SQL
+	* use knowledge of scheme to deduce types and do type checking
+
+* errors reported by exceptions only (so write code on assumption of success)
+
+For an example of usage, see test/database/testdatabase.cpp
+
+
+SUBTITLE Connecting to a database
+
+The DatabaseConnection object represents a connection to the database, which is connected when the Connect() method is called, and disconnected when the object is destroyed. For example
+
+DatabaseConnection db;
+db.Connect(std::string("sqlite"), connectionString, 1000 /* timeout in ms */);
+
+This will either connect or exception.
+
+The first parameter is the driver name. The drivers available depend on what is compiled into the application (based on what was available when the code was compiled).
+
+The second is the connection string, which is driver dependent, and specifies everything the driver needs to know to make the connection. For database servers, this is hostname, username, password and database name, for SQLite it's simply the filename of the database.
+
+The Timeout is the connection timeout, in milliseconds. Some drivers may interpret this slightly differently, but all in an appropraite manner.
+
+
+
+SUBTITLE Making a query
+
+The DatabaseQuery object makes a query to the database. By itself, it cannot do anything useful, and a derived class must be used (for example, DatabaseQueryGeneric).
+
+Applications should not generate SQL statements at runtime. Instead, a system of parameters is used to efficiently insert variable data into SQL. For example
+
+  INSERT INTO table (field1,field2) VALUES ($1,$2)
+
+The SQL statement is constant, but at runtime, the two values $1 and $2 are passed as arguments to the Execute() function. Strings need not be quoted. NULL values are passed as NULL pointers instead of pointers to an actual value.
+
+The exact way arguments are presented depends on the derived class -- in general, the derived class will have an Execute method which allows for type-safe arguments (mainly with autogenerated code).
+
+The same query can be executed many times, with different arguments. Some drivers will handle this very efficiently if the statement can be "prepared".
+
+Once the query has been executed, the results can be read.
+
+If the query does not return data (eg an INSERT statement), then GetNumberChanges() will return the number of changes made.
+
+Otherwise, data must be retrieved. GetNumberRows() and GetNumberColumns() return the number of rows and columns respectively.
+
+To read all the fields out, use a loop like
+
+while(query.Next())
+{
+	// handle row
+}
+
+Within the loop, use GetFieldInt() and GetFieldString() with the column number to retrieve row values. (Given the database overhead, it is recommended that if a value is used repeatly, the GetField*() method is only called once, with the value stored in a local variable.)
+
+With autogenerated code, suitable GetFieldName() functions will be generated, which return named fields.
+
+If the query returns a dataset of 1 row and 1 column exactly, then GetSingleValueInt() and GetSingleValueString() can be used to retrieve that one value without the necessity to call Next(). This is provided for convienence.
+
+
+SUBTITLE Generic query object
+
+DatabaseQueryGeneric is a generic query object which can be used for simple queries where it's not worth using autogenerated code.
+
+The basic SQL statement is passed into the constructor, and then several Execute methods are provided; no parameters, 1 int or string parameter, or many parameters.
+
+The many parameters method is declared as
+
+	void Execute(const char *Types, ...)
+
+The first argument specifies the type of the following arguments, and implied by the length of the string, the number of arguments. Little type checking can be done -- for complex queries taking many arguments, use autogenerated code! (Or where there are many columns to return, where it's nicer to use names than numbers to refer to them.)
+
+Each character in the string defines one argument. Values are
+
+i = integer (value, cannot be NULL)
+I = integer (pointer, can be null)
+s = const char * string (pointer, can be NULL)
+S = std::string (pointer, can be null)
+N = null (corresponding argument must be NULL or 0)
+
+For example,
+
+int i1 = 24;
+char *str1 = "test string";
+std::string str2("Test string 2");
+query.Execute("NiIIsssSS", NULL, 56, NULL, &i1, NULL, "constant string", str1,
+   NULL, &str2);
+
+Values are read out as any other DatabaseQuery object.

Added: box/features/codeforintegration/docs/webappframework/web_application/Code.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Code.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Code.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,93 @@
+TITLE C++ Code
+
+The web application framework generates C++ code. Extra code is added by the developer of the application to perform application specific tasks. As well as snippits of code supplied to units (for example, the post submission code for a Form) you can add arbitary code using the Code unit.
+
+
+SUBTITLE Pseudo functions
+
+The code will need to access other entities in the page structure, for example, page variables. This complicates things, because the information to resolve these is not available until the first pass of the tree when writing the C++ files.
+
+To get around this, a number of pseudo functions are provided. These are translated into actual C++ code when the C++ file is written. They are all in the 'WAF' namespace.
+
+They have a few pecularities:
+
+* They look like C++ functions, but are handled internally by translation.
+
+* You can't use ( ) brackets anywhere in the parameters (regex limitation).
+
+* The arguments, while looking like C++, are actually perl arguments and data structures.
+
+* If a function is cast to another data type, this actually changes the return type of the generated code rather than taking the original type and casting it. In effect, it is an implicit conversion, which will exception if the data being converted in invalid.
+
+* You can only use them within the handler function in the page language files, because they need to access local variables in that function.
+
+
+SUBTITLE WAF::Var('page_variable')
+
+Replaced with code evaluating to the page variable. Use a cast to convert between types. The type is the default type of that variable.
+
+
+SUBTITLE WAF::Link(<link specification>)
+
+See "Generating links to other pages" in Units.txt for information on how to form a link specification. Note this is in perl notation, even though it's supposedly C++ code.
+
+This will be substituted for an expression evaluating to a std::string containing the URL of the page.
+
+This is not the most efficient way of writing a link. If possible, use a Unit which will call write_page_address() to output the URL, as it will take advantage of unchanged parameters and other optimisations.
+
+It is an error to try and cast this function.
+
+
+SUBTITLE WAF::SetCookie('cookie_name', 'page_variable')
+
+Substituted with code which will set the cookie to the value of the specified page variable. The namespace for the Cookie is '/app_name' so that the cookie is only returned for pages within this web application.
+
+
+SUBTITLE <<<<Translated and Interpolated Text>>>>
+
+If there is some text bounded by <<<< and >>>>, it is replaced by an expression evaluating to this text in std::string. The text is translated using the normal mechanism, and any page variables surrounded by {} have their values interpolated into the string. For example
+
+std::string text(<<<<This some text.
+
+Username {params.Username}.
+>>>>);
+
+Whitespace is respected (as long as the translations expect it). There is no compulsion for a translation to include every page variable, in fact it could include more.
+
+
+SUBTITLE Code Unit
+
+The WebAppFramework::Unit::Code unit takes two parameters:
+
+Code: The code to insert into the generated page.
+Phase: Either the phase name, or an array of phase names, when the code should be inserted.
+Headers: List of header files to include on relevant page.
+SystemHeaders: List of system headers to include.
+
+Phase names are:
+
+When generating the global code file:
+
+	global_declaration - cpp file, declare objects
+	global_code - cpp file, code
+	global_h - h file
+
+When generating the language C++ file:
+
+	lang_declaration - declarations before the handler function
+	lang_vars - declaration of variables within the handler function
+	lang_security - security handling code
+	lang_prepare - code to prepare before writing HTML (can only redirect here)
+	lang_output - code to output the HTML
+	lang_finish - code to clean up after generating the HTML
+
+When generating the main C++ file for a page:
+	
+	main_declaration - declare vars and functions
+	main_code - Implement functions
+
+And finally, the .h file for the page:
+
+	main_h - declaractions available for main and language C++ files
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/DataSource.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/DataSource.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/DataSource.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,57 @@
+TITLE DataSource units
+
+DataSource units are derived from WebAppFramework::Unit::DataSource, and represent a source of data, in the form of ordered (key,string) pairs. They are designed for use in Choice fields in forms, but are generic enough to be used elsewhere.
+
+This document lists the available data sources, and documents how to write a new DataSource object.
+
+
+SUBTITLE WebAppFramework::Unit::DataSource::DatabaseQuery
+
+Parameters:
+ 	Query => database query to use, or an anonymous hash of the Query parameters.
+	Args => Arguments for query
+
+This source takes a database query, or a definition for a query. The query must have two columns, the first is the 'key' used by the system and the second is the 'string' displayed to the user.
+
+The 'Args' parameter can be used to specify the sources of data for the parameters of the query, in the usual notation.
+
+
+SUBTITLE WebAppFramework::Unit::DataSource::CppSTLContainer
+
+Parameters:
+	Container => page variable containing typed container variable
+	IgnoreBlank => if set, blank strings are ignored
+
+This source iterates over a STL container. The container is specified as a page variable, but most likely will be something other than a variable registered by a Unit. For example
+
+  'Container' => '=std::vector<std::string> containerName'
+
+If the container contains a std::pair (or implicitly as a std::map) then first is used as the key, and second as the string. If not, an integer key is assumed as the location in the vector.
+
+
+SUBTITLE WebAppFramework::Unit::DataSource::StaticStrings
+
+This object is primarly used by the Choices unit to automatically write out large numbers of choices efficiently.
+
+It's one parameter, Strings, is either a | separated list of strings to translate, or a reference to an array of strings which are NOT translated.
+
+
+SUBTITLE WebAppFramework::Unit::DataSource::Null
+
+This is a DataSource which emits no data, and only writes blank code.
+
+
+
+SUBTITLE Creating a DataSource
+
+DataSource objects are full Units, and will always be placed as sub-units of the units which use them. This is so that they can take advantage of the Unit infrastructure to include relevant header files and write code in addition to the actual data loop.
+
+The main function a DataSource must implement is write_bound_item(). This writes the loop which will output the data. It's arguments are a reference to a function, and the output, phase and subphase.
+
+The function will write the code which uses the data. The DataSource is expected to write code which iterates over the data, and so will execute the code written by this function once per item.
+
+The function takes two C++ expressions which evaluate to the key and string respectively. The key can be either an integer or a string, but must correspond to the result of the key_is_integer() method.
+
+By default, the string is written 'de-fanged', which is not as efficient as just writing the string out. If the string_is_trusted() method returns true, it is simple written out. But this should only return true if the string is sourced from the application author or translators.
+
+Finally, if the DataSource knows either of the limits of an integer key at compile time, it can return the (min,max) values from get_integer_range(). These are used as an additional validation for the form values. Either can be undefined, in which case the corresponding check is omitted from the generated code.

Added: box/features/codeforintegration/docs/webappframework/web_application/Database.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Database.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Database.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,72 @@
+TITLE Database integration
+
+Units are provided which perform common tasks for database access.
+
+All database objects assume that the mApplication member variable of the application (derived from the WebApplicationObject class) has a method of the form
+
+   DatabaseConnection &GetDatabaseConnection();
+
+which returns a connected database connection for the various queries to be run on.
+
+
+SUBTITLE Creating a C++ query object
+
+Use the Unit WebAppFramework::Unit::Database::QueryObject to generate the C++ class for a query. The written object can then be used in your code.
+
+This takes two parameters, 'Query', which is either a Database::Query object or a ref to a hash array of the Query parameters, and 'Where', which specifies where the object is to be written. This can be 'Global', where the object is written to the global file and is available everywhere (make sure it's created in the setup_page() function), 'Page' for the current page, and 'Language' for the current language only. If Where is not specified, it defaults to 'Page'.
+
+
+SUBTITLE Running a query when a form is submitted
+
+The object WebAppFramework::Unit::Database::OnSubmitExecSQL will run a one or more queries when a form is submitted. Simply add it as a unit to the form object, and the magic will happen.
+
+$form->add_unit('OnSubmit',
+	WebAppFramework::Unit::Database::OnSubmitExecSQL->new(...));
+
+Parameters:
+
+Query: A Database::Query object (see lib_database/AutogenQuery.txt), or a anon hash array of the parameters, or a anon array of either.
+Args: Override data sources (see below)
+RedirectTo: (optional) Link specification for a redirect when the query is executed.
+PreExecuteCode: Additional code to execute when the form is submitted but before the query is executed.
+PostExecuteCode: Additional code to execute when the form is submitted but before the query is executed.
+
+Queries specified as hash arrays of paramters do not need a name explicitly set. A name will be generated if needed.
+
+By default, all the parameters from the query are filled in from the form items in the form which have the same names (as in the Query parameters specification.) This means that you can insert values into a database query without even having to specify which ones go where.
+
+However, you may need to override this behaviour for certain fields. Args is an anonymous array, which contains a list of parameter name and data source pairs. The data source is a standard page variable. For example, 
+
+    'Args' => ['CreatedBy' => 'params.Username'],
+
+retrieves a value from the page parameters.
+
+RedirectTo is a standard link specification. However, if a data source is 'QUERY_AUTO_INCREMENT_VALUE', then it will be replaced with the auto-increment value of the query. You will need to include the AutoIncrementValue paramter to the Database::Query object. This will only work if there is a single query with an AutoIncrementValue set.
+
+If multiple queries are specified, then they are executed in order. The AutoIncrementValue is made available from each which specifies it in the database paramters.
+
+If PreExecuteCode is present, then that code will be output just before the generated code to execute the query.
+
+If PostExecuteCode is present, then that code will be output just after the generated code has executed the query. If there's an auto-incremented value in the query, then autoIncrementValue will be set to the value this value.
+
+
+SUBTITLE Running a query when a page is loaded
+
+It's often useful to a run a query when the page is loaded, for example, using an ID from the page parameters to fetch a row from the database table. WebAppFramework::Unit::Database::ExecuteQuery will do this for you, and then register the namespace of the query so that the values can be picked up as page variables.
+
+Parameters:
+
+Name: Name of the object (will be registered as a namespace)
+Query: Database::Query object which should be executed
+Args: Arguments for the Execute() function on the query
+RedirectToOnNoRow => Page to redirect the user to if there's no row available (optional)
+
+RedirectToOnNoRow is a standard link specification.
+
+Note that this object should probably be added as a post unit onto the page root object. Otherwise it may end up being executed too early before other objects (such as forms) have been initialised and their values are valid.
+
+
+SUBTITLE Displaying the results from queries
+
+See DatabaseDisplay.txt
+

Added: box/features/codeforintegration/docs/webappframework/web_application/DatabaseAuthenticate.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/DatabaseAuthenticate.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/DatabaseAuthenticate.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,161 @@
+TITLE Authentication against a database
+
+The WebAppFramework::Unit::Database::Authentication unit allows users to be authenticated against a database.
+
+
+SUBTITLE Concepts
+
+A user is identified to the system by a ID, and authenticated using a Token.
+
+The ID might be the textual username, or a numeric user ID. The Token might be the password, or the password hashed with a secret, or even a random number allocated on each login.
+
+Credentials are the combination of ID and Token in a single string for convenience. The Credentials are obtained automatically from a specified page variable by the unit -- the source could be a cookie, a form variable, or a page parameter.
+
+
+SUBTITLE General usage
+
+First of all, decide what the ID and Token will be. If it's not the username (as entered by the user) and the password (perhaps filtered) then you'll have to write more custom code -- but it's not too difficult.
+
+The authentication is based around a query. This is an example:
+
+	my $security = WebAppFramework::Unit::Database::Authenticate->new(
+		'Name' => 'Security',
+		'Query' => {'Statement' => 'SELECT fID,fUsername,fPassword FROM tUsers WHERE fUsername = $1',
+					'Parameters' => 'std::string Username',
+					'Results' => 'int ID, std::string Username, std::string Password'},
+		'TokenColumn' => 'Password',
+		'CredentialsSource' => 'cookie.Credentials',
+		'RedirectToOnAuthFailure' => ['Login'],
+		'DisableRedirectOnPages' => 'Login');
+
+(The Query parameter can either be a Query object, or an anonymous hash array to it's parameters, optionally omitting the name. In the latter case, a suitable query is generated.)
+
+The query should
+
+* Have one or two parameters (corresponding to the Username, and optionally the Token if the token is directly stored in the database)
+
+* Obtain as many columns from the table as is useful for the rest of the page. They will be made available in the namespace given for the 'Name' parameter.
+
+* Return the Token, or the column which will be transformed into the Token. The 'TokenColumn' should be set to the name of this column, so the unit can find it.
+
+The 'Results' from the Query object will be availabile via the page variables, but only if authentication worked. If they are used in any other case, the database driver will throw an exception.
+
+The 'CredentialsSource' parameter specifies where the credentials are obtained from. This can only really be a cookie, something in formdata, or a page parameter, as nothing else will have evaluated before then. The query is exected before anything else, in a special SECURITY phase.
+
+If 'RedirectToOnAuthFailure' is a link specification, then the browser will be directed to this link if the authentication failed. Otherwise, the Name.IsAuthenticated page variable will evaluate to false.
+
+Of course, some pages shouldn't be redirected away from if the authentication fails, for example, the login page to allow the user to authenticate! Disable specific pages in using the 'DisableRedirectOnPages' parameter.
+
+If the credentials are required for any reason, the Name.Credentials page variable will retrieve them after successful authentication.
+
+
+SUBTITLE Filtering the token
+
+Using the examples shown does result in the user's password being visible either in the URL or in a cookie. The token can therefore be optionally filtered by running it through a MD5 hash. To enable this, add the following parameters to the Authenticate unit:
+
+		'TokenFilter' => 'MD5',
+		'MD5SecretConfigVar' => 'CredentialTokenSecret',
+
+The first specifies that MD5 should be used to filter the token, the second the source of a secret to add to the password in the hash. This is obtained from the applications configuration file -- be sure to add that name to the generated configuration verification by adding this in the main application description file:
+
+$webapp->add_extra_config_directive('string', 'CredentialTokenSecret');
+
+
+SUBTITLE Logging in
+
+The process of logging in is expected to use a form, which will only validate correctly if the password is correct. This allows the log in process to use the existing mechanism for displaying form errors.
+
+Once the form is submitted, the 'HandleSubmission' code should generate the credentials from the security object using the MakeCredentials(id, token) function and set the source of the credentials to this value:
+
+Cookie: Use rResponse.SetCookie()
+
+Persistant form data: Set whatever local variable will ensure that the credentials will be carried through to future requests.
+
+Page parameter: Redirect the user to a new page using the link specification to place the credentials in the URL.
+
+
+SUBTITLE Generating Validation code
+
+(See the example validation code below for details of what this generates.)
+
+In the Form unit, set the following parameters:
+	'FormValidation' => 'simple',
+	'ArgsToValidate' => 'Application'
+
+You will probably need to find the authentication unit from the default page:
+
+my $auth = $page->find_unit('Authenticate', 'Name' => 'Security');
+
+(changing the name as a approprate.) Then use the set_validate_function_on_form(webapp, form, id_field, token_field) method, where webapp is the web application object, and form is the relevant form. id_field and token_field are the fieldnames within the form corresponding to the id and token respectively. For example:
+
+$auth->set_validate_function_on_form($webapp, $form, 'Username', 'Password');
+
+If you need to include extra validation in the function, use $auth->generate_validate_code(id_field, token_field), which returns a string containing the body of the function generated above, for inclusion into a function which you write.
+
+
+SUBTITLE Generating Submission handling code
+
+(See the example submission code below.)
+
+Use the set_HandleSubmission_on_form(form, id_field, token_field, link_spec) method to set suitable HandleSubmission code for a form. For example,
+
+$auth->set_HandleSubmission_on_form($form, 'Username', 'Password',
+	['Main', 'Username' => 'login.Username', 'Value' => 'CONSTANT:1']);
+
+This will set the credentials in the approprate place (using CredentialsSource parmater of the Autenticate unit) and then redirect to the given link specification.
+
+If a "params" source is specified, the given link specification is modified automatically. Do not include the parameter in the list.
+
+
+SUBTITLE Example validation
+
+The following snippet of code writes a suitable validation routine. Note that the form unit must be set to pass the Application objects to the validation function. ('ArgsToValidate' => 'Application')
+
+my $validation = WebAppFramework::Unit::Code->new('Phase' => 'main_code', 'Code' => <<__E);
+	void TestWebAppFormLogin::Validate(TestWebApp &rApplication)
+	{
+		SecurityBase query(rApplication.GetDatabaseConnection());
+		query.Execute(mUsername);
+		if(query.Next())
+		{
+			if(query.GetPassword() == mPassword)
+			{
+				mPasswordValidityError = WebAppForm::Valid;
+			}
+		}
+	}
+__E
+$page->add_post_unit($validation);
+
+The name of the Authentication unit here is 'Security'. This uses the query object generated, 'SecurityBase', and executes it. If the username is found, the password is checked and if correct, the password field is marked as valid.
+
+
+SUBTITLE Example submission handling
+
+This example sets the submission handler on the form to set a cookie with the required credentials.
+
+$form->set('HandleSubmission' => <<__E);
+		// Set the credentials
+		std::string credentials(Security.MakeCredentials(login.GetUsername(), login.GetPassword()));
+		rResponse.SetCookie("Credentials", credentials.c_str());
+
+		// Set redirect
+		std::string uri(WAF::Link('Main', 'Username' => 'login.Username', 'Value' => 'CONSTANT:1', 'Text' => 'CONSTANT:start'));
+		rResponse.SetAsRedirect(uri.c_str());
+
+		return true;
+__E
+
+Firstly, it obtains the credentials using the username and password from the form, 'login', sets a cookie with this value, and then redirects the user to another page.
+
+
+SUBTITLE Logging out
+
+When loggin out, simply reset the source of the credentials to the empty string, and call the SetToUnauthenticated() method. For example, to log out from a log in by the above example, use
+
+my $logout = WebAppFramework::Unit::Code->new('Phase' => 'lang_prepare', 'Code' => <<__E);
+	login.SetToUnauthenticated();
+	rResponse.SetCookie("Credentials", "");
+__E
+$page->add_post_unit($logout);
+

Added: box/features/codeforintegration/docs/webappframework/web_application/DatabaseDisplay.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/DatabaseDisplay.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/DatabaseDisplay.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,118 @@
+TITLE Displaying the results of database queries
+
+The WebAppFramework::Unit::Database::DisplayQuery unit will display the results of a query, outputing it's sub-units once per row of a query.
+
+The WebAppFramework::Unit::Database::Table unit is derived from DisplayQuery, and will display the results of a query in an HTML table, with various options for display, including manipulation of headings and columns.
+
+Future enhancements:
+
+* Paging of results
+* Sort order by clicking on headings
+
+These will be implemented by addition attributes to the unit.
+
+
+SUBTITLE WebAppFramework::Unit::Database::DisplayQuery
+
+This unit is based on the FragementsTemplate unit, so takes the two attributes
+
+Template: base name of template filename
+FragmentsName: name of fragments to pull out of the file
+
+These are most likely set in the application setup using the defaults. See below for the fragements expected.
+
+Then, the query is specified like this
+
+Name => Name of the object (will be registered as a namespace)
+Query => Database::Query object which should be executed
+Args => Arguments to query (optional)
+
+The arguments are specified as for the ExecuteQuery unit, eg
+
+	'Args' => ['CreatedBy' => 'params.Username'],
+
+and the source is either CppVariable objects or page variable names.
+
+To add more code before and after the query is executed, to prepare variables or extra information, use
+
+PreQueryCode => code to output just before the query is made
+QueryCode => code to output to execute the query
+PostQueryCode => code to output just after the query is executed.
+
+The name of the query object is specified in the Name parameter.
+
+
+The query will be executed, and then the sub-units displayed once per row. Use the registered namespace to access the results of the query. For example, if a query specifies
+
+  'Results' => 'std::string Text,int32_t Integer'
+
+and the Name of the DisplayQuery object is 'query', then to display the entries you could add a TranslatedText query with the text "Text: {query.Text}, Integer: {query.Integer}<p>" to display the results, one per line.
+
+
+SUBTITLE Queries with runtime statements
+
+If the query has a runtime statement (with "runtime" as Statement) then you need to supply the Query code to execute this statement. This includes creating the correct object with the same name as the name of the Unit.
+
+Tip: Write the code at first without the runtime query, then copy and paste the code from the generated language cpp file, and modify it.
+
+
+SUBTITLE WebAppFramework::Unit::Database::Table
+
+This unit takes all the parameters of DisplayQuery, plus
+
+HideFields => Array of fields to hide. (optional)
+
+In the table, the number of columns, order, and default headings are deduced from the 'Results' attribute of the query object.
+
+
+SUBTITLE Fragments
+
+See FragementsTemplate documentation in TemplatesUnits.txt. The following fragment names are expected:
+
+Begin - beginning of table
+RowBegin - begin a row (used for headings and data rows)
+HeadingCellBegin - start of a heading entry
+HeadingCellEnd - end of a heading
+DataCellBegin - start of a data entry (repeated)
+DataCellEnd - end of a data entry
+RowEnd - end a row
+End - end the table
+
+
+SUBTITLE Adjusting the display of data
+
+To display a field in a different way to the default, which is to simply convert it to text, add a unit at the position <Field>_Display. So if you had a field called Colour which was to be displayed different, add a unit at Colour_Display. This unit can retrieve the value to display from the page variable name.Colour (where 'name' is the Name attribute specified for the Table object).
+
+To add additional columns, add units with specific names. To add a column before a field, add Column_Before_<Field>. To add a column at the right hand side, add Column_Last. If you want to have multiple added columns in any one space, append _<anything> to the name, and they will be added sorted in alphabetical order.
+
+In all these columns, use page variables in the units to retrieve the value of the fields of the query.
+
+
+SUBTITLE Adjusting the headings
+
+The default heading for a column is the name of the result field (specified in the Query object) with spaces inserted before capitalised words. So MultiWordField would have a heading "Multi Word Field".
+
+For a simple data cell, the heading can be adjusted by adding a unit with the position <Field>_Heading, for example, to change the heading of the column which displays the field 'Colour', add a unit for Colour_Heading. This can most easily be done like this
+
+  $list->add_text('Colour_Heading', 'Item colour');
+
+if you just want to change the text without doing anything more fancy.
+
+To insert headings for other columns (ie ones inserted as above) simply add a unit for <pos>_Heading, where <pos> is the position of the Unit which displays that column. So if you added Column_Last_N1, the heading position should be Column_Last_N1_Heading.
+
+
+SUBTITLE Adding action links
+
+To add links to the last column, the most convenient way is to use the WebAppFramework::Unit::ListOfLinks unit, and link to pages which perform the actions, specifiying page parameters as variables from the query. For example,
+
+$list->add_unit('Column_Last', WebAppFramework::Unit::ListOfLinks->new(
+	'Links' => 
+		[
+			['Display', ['DisplayEntry', 'EntryID' => 'entry.ID']],
+			['Delete', ['DeleteEntry', 'EntryID' => 'entry.ID']]
+		]
+	));
+
+will add "Display" and "Delete" links.
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/DatabaseNewOrEdit.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/DatabaseNewOrEdit.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/DatabaseNewOrEdit.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,32 @@
+TITLE Database NewOrEdit Pattern
+
+It is a common requirement to create and then edit items in a database. To avoid having to write two forms and two sets of handling code, the WebAppFramework::Unit::Database::FormNewOrEdit unit allows a single form to be used for both.
+
+The FormNewOrEdit unit will modify the form accordingly, and write all the code to take the different actions depending if the form is in new or edit mode.
+
+Fields can be made conditional in the edit version, for example, replacing them with data read from the database. To change the page title, use OutputIf units. To change the label on a submit button, add an OutputIf unit in the Label position.
+
+There are three sets of (potentially) multiple database queries specified. The New queries, for creating a new object. The Read queries, for reading existing data out of the database. And finally, the Update queries, for modifying an existing object when the form is submitted.
+
+There are lots of parameters to the new statment:
+
+NewCondition => C++ expression evaluating to true when the form is set to create a new object
+ReplaceFieldsInEdit => ['FieldName' => 'Replacement', ...] to replace fields when the form is in edit mode
+QueryNew => query(s) to create the object
+QueryRead => query(s) to read existing data about the object from the database
+QueryUpdate => query(s) to update the object in the database
+ArgsNew => For the New query(s), override finding of arguments from the form object
+ArgsRead => For the Read query(s), override finding of arguments from the form object
+ArgsUpdate => For the Update query(s), override finding of arguments from the form object
+ReadQueryNamespace => If present, the read query is registered as the named namespace, for access via page variables (in edit mode only!)
+ItemOverrideForUpdate => ['FieldName' => 'default', ...] to override then initial state for the update form
+RedirectTo => Page to redirect the user to after success
+RedirectOnNoReadResults => Page to redirect the use to if there are no results from the read
+PreExecuteCode => Any additional code to write before the database query is run
+PostExecuteCode => Any additional code to write after the database query is run
+
+The form this refers to must be a positioned sub-unit of this unit. The defaults in the items of the form should be set to those for the new version. Defaults will be modified accordingly.
+
+In the Query* parameters, the data is a Database::Query object which should be executed, a hash array of Query constructor args (names automatically generated if not specified), or a array of Database::Query or hash arrays to represent a list of queries which should be executed.
+
+If a New or Update query has an auto-increment value, then it will be made available in the local variable <name>_AutoIncrementValue where <name> is the given name of the query. It can therefore be accessed as a page variable like '=int32_t query_AutoIncrementValue'.

Added: box/features/codeforintegration/docs/webappframework/web_application/Example.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Example.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Example.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,78 @@
+TITLE Web application framework example
+
+The test for the generic web application framework is test/webappframework, which implements a simple application which can be viewed in two languages. This document is a guided tour of the files involved in this example application.
+
+
+SUBTITLE TestWebApp.cpp and TestWebApp.h
+
+Each application has a class which implements global functionality. This class is included as a data member of the mini HTTP server generated by the framework, and assessible as mApplication in all generated functions.
+
+
+SUBTITLE Templates/TestWebAppMain.en.html
+
+This is the HTML template for the entire application. Notice the place markers for things such as title and page position, how some of the file is omitted, and the fragments for the TemplateContainer marked with HTML comments.
+
+
+SUBTITLE TestWebApp.pl
+
+This is the application setup file. It
+
+* Sets the web application name, the daemon name, and the base URL for the web application
+
+* Adds two languages, and sets the default to 'en' (English)
+
+* Sets to global parameters, which are required for every single page. Note how the parameters are strongly typed with their C++ types.
+
+* Defines some pages, and their paramters. This must be done in the global setup file, as linking to a page requires knowledge of it's paramters.
+
+* Sets some defaults for the Units. This is used to avoid having to specify the template name for every single new() of a unit.
+
+* Defines the setup_page() function, which returns a pre-initialised root Unit for every single page.
+
+The setup_page function is called every time a page is to be generated. This example creates a root unit using PageTemplate. A menu object is inserted in the MENU marker position (at the moment, just a text string).
+
+A status TableContainer is created, which displays several variables. One of the labels is linked to another page, taking paramters from the global paramters and a constant.
+
+
+SUBTITLE Pages
+
+This directory contains the page description scripts.
+
+
+SUBTITLE Pages/Main.pl
+
+Each of the page scripts adds the page title, using an add_text() call for the TITLE marker.
+
+This adds a TableContainer in the main page marker, and text in various cells. Since this is all constant at compile time, you can see in the generated code that it is all output as one chunk of HTML, except for the one bit of dynamic HTML generated by the link in the bottom right cell.
+
+
+SUBTITLE Languages
+
+English is the default language for this application, and there is a translation into language 'CAPS', which is just English converted into upper case (a nice simple machine translation for the example.)
+
+makeCAPStranslation.pl is a script which generates the translated files. Templates/TestWebAppMain.CAPS.html is a copy of Templates/TestWebAppMain.en.html with all the text changed to upper case. (NOTE: A clean rebuild of the system will be required to see the results properly after running this script.)
+
+Languages/CAPS.txt is read in, and all translated text is converted into upper case. Note that the page names and the default language (lines starting with #) are not changed -- this would break the system.
+
+
+SUBTITLE Makefile.extra
+
+This Makefile fragment calls the main script to generate Makefile.webapp and the server class. 
+
+
+SUBTITLE Makefile.webapp
+
+This is automatically generated, and contains all the commands to generate the code for each page.
+
+
+SUBTITLE autogen_webapp/TestWebAppServer.*
+
+This implements the mini-HTTP server for the web application. Note the dispatch function in the .cpp file.
+
+
+SUBTITLE autogen_webapp/*Page*.*
+
+These C++ files implement the actual page HTML generation. There is one main file which contains all code common to all languages, and one .cpp file for each language. The language .cpp files contain a map of the Units used to generate the page -- which could be different for each language.
+
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/FixedPoint.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/FixedPoint.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/FixedPoint.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,24 @@
+TITLE Fixed point integers
+
+The framework supports the use of fixed point numbers in various places, notably the NumberField form item.
+
+Since these numbers will be generally displayed to humans, they are implemented with multiplication factors of powers of ten. This avoids problems with number representation causing different values to be stored than the user expected.
+
+Two values are used to specify the fixed point parameters. Firstly, the number of scale digits. This is effectively the power of 10 which the real number is multipled by to get the fixed point number. In addition, there is a number of display digits, which is the minimum number of digits which will be displayed when formatting the number. For example, for currency display you would use a value of 2.
+
+
+SUBTITLE NumberField form item
+
+See FormItems.txt
+
+
+SUBTITLE FixedPointNumber unnit
+
+WebAppFramework::Unit::FixedPointNumber outputs a formated fixed point number
+
+new() parameters:
+	Variable => name of variable
+	ScaleDigits => number of digits for (base 10) fixed point values
+	DisplayDigits => min number of digits to use for the fractional part. Defaults to ScaleDigits.
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/FormContainers.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/FormContainers.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/FormContainers.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,11 @@
+TITLE Form Containers
+
+Form Containers are simply Units which contain other objects, and are using with Forms. They implement a few extra methods to support Form functions. None is strictly necessary -- the Form object will provide sensible defaults.
+
+
+SUBTITLE get_form_template_fragment(fragment_name)
+
+Returns a text fragment for use in templating (not subject to translation), or undef if the default should be used. The null string may have special meaning.
+
+See Forms.txt, Form Unit subsection, for a list of markers which could be supported.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/FormItems.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/FormItems.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/FormItems.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,142 @@
+TITLE Form items (fields)
+
+This document describes the form items implemented.
+
+
+SUBTITLE TextField
+
+This implements a text (or password) input field.
+
+Attributes are:
+
+Name - Form item name
+Default - source of default value (variable, can be CONSTANT:value for constant values)
+DisplayAsPassword - if present, then display the field as a password entry
+Size - size of field (either x or x,y for textarea)
+MaxLength - max size of entered text, if browser respects this
+Validation => Validation information
+ValidationFailMsg => Text to display when validation fails, if default isn't good enough
+BlankAllowed - if present, then a blank field is an acceptable value which passes validation
+
+Validation options are:
+
+none -- always passes validation
+external -- always failes validation, and will be checked externally
+length(min,max) -- length is in a certain range. Omit either min or max for unbounded ranges
+x(y) -- name and arguments for a standard validator. See Validators.txt.
+reference -- or a reference to a Validator object
+
+If the size field is not present, or a single number, a normal text field is output. If the size field is of the form "x,y", for example, "4,8" then a textarea is output, with the set sizes.
+
+Note that email type validation ('email' or 'email(no-lookup)') will remove whitespace from the entered text, and requires lib/smtpclient to be included in the depenencies for the app in modules.txt.
+
+If BlankAllowed is set, then additional code is written to accept a blank value as valid input completely independently of the other validation options specified.
+
+
+SUBTITLE NumberField
+
+This implement an integer number input field.
+
+Attributes are:
+
+Name - Form item name
+Default - source of default value (variable, can be CONSTANT:value for constant values)
+DefaultNumber - default for data member in form. Should only be displayed when the number is outside the normal int32_t range
+FixedPointScaleDigits - number of digits for (base 10) fixed point values
+FixedPointDisplayDigits - min number of digits to use for the fractional part. Defaults to FixedPointScaleDigits.
+Validation => Validation information
+ValidationFailMsg => Text to display when validation fails, if default isn't good enough
+BlankValue - if set, then blank entries are allowed, and the field data value will be set to this value
+Size - size of text field (maxlength is set automatically to something which is appropraite for the range allowed)
+
+Validation options are:
+
+none -- no range validation, but number must be entered unless BlankValue set
+range(min,max) -- must be within range, omit for unbounded values.
+
+Note: When fixed point in use, specify all numbers in human readable form, eg 1.01
+
+See FixedPoint.txt for definitions of ScaleDigits and DisplayDigits.
+
+
+SUBTITLE SubmitButton
+
+This implements a submit button for the form.
+
+Attributes are:
+
+Name - Form item name
+Text - Text to be displayed in the button, subject to translation
+Report - If set, will report on whether the button was clicked.
+
+If reporting of clicks is on, then the name of the variable will be the Name + 'Clicked' to distinguish it from a value result.
+
+If a 'Label' sub-unit is added, then the Text parameter will be ignored and that unit output as a lable. Be careful that this unit just outputs text. Use this to output variable labels.
+
+
+SUBTITLE Checkbox
+
+This implements a checkbox for the form. Checkboxes never have validation, and are always passed as validated. (Use custom validation otherwise.)
+
+Attributes are:
+
+Name - Form item name
+Label - Value text (will be translated)
+Default - source of default value (can be CONSTANT:true/false for constant values)
+
+
+SUBTITLE Choice
+
+A flexible item to allow the user to choose from a static list of items. Multiple and single choices are allowed and validated, and the list can be shown in select (HTML select entity) or items (checkboxes or radio buttons).
+
+Attributes are:
+
+Name - Form item name
+Choices => | separated list of choices (will be translated)
+				(only if DataSource not used)
+Default => see below
+Style => select (HTML select widget) or items (checkboxes or radio buttons)
+Size => rows visible (for select mode only)
+Columns => number of columns (for items mode only)
+Validation =>
+	none	- no checking (single choice widget)
+	single	- single choice must be made (single choice widget)
+	choices(x,y)	- numbers allowed within range x to y inclusive. Omit for unbounded range. (multiple choice widget)
+
+Default specified either a constant value, as CONSTANT:entry (where entry is an untranslated default choice from the Choices list), or a variable, which should convert to an integer which is a zero based item number. If this is not included, a blank item will be displayed.
+
+The variable in the data object, for single selections, is an zero based int32_t index of the item selected, or -1 for no selection. For multiple selections, a std::vector<int32_t> is used to list all items, in no particular order.
+
+Alternatively, the list of choices can be determined from a DataSource, by adding a data source unit in the 'DataSource' position. If the 'key' of the data source is a string, then the form variable will be a string, otherwise an integer as per normal. See the DataSource.txt file for more information.
+
+
+SUBTITLE Date
+
+An item to request a date using three drop down lists. Order of these items is controlled by the current Locale object. The date is validated, including knowledge of leap years.
+
+The data member is of type WAFFormItemDate, call GetYear(), GetMonth(), GetDay() to retrieve the values.
+
+Attributes are:
+
+Name => Form item name
+Default => standard date specifier (optional)
+Optional => set if the field is optional
+StartYear => first year displayed
+EndYear => last year displayed
+ValidationFailMsg => text to display on error
+
+See Locale.txt for the specification of the standard date specifier.
+
+
+SUBTITLE ExtraDataMember
+
+This is primarily used to pass extra variables out from a validation function. It has a single attribute:
+
+Variable => CppVariable style variable specification
+
+For example:
+
+$form->add_unit('extra', WebAppFramework::Unit::FormItem::ExtraDataMember->new('Variable' => 'int32_t ValidatedID'));
+
+will add an extra data member and accessor function, which can be set in the Validate function and access outside via page variables.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/Forms.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Forms.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Forms.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,167 @@
+TITLE Forms
+
+Forms are the basic means of interaction with the user, other than clicking on links with embedded parameters. There can be more than one form per page, each with a one character ID unique within the page.
+
+The form Unit writes two bits of code, the HTML for the form and the code to handle that form. These can be separated, so that one page outputs the form and another handles it (the ID for the form must be the same on both pages).
+
+This is useful for global forms, such as search boxes, have optimisations which result in simple HTML with no variable subsitution being output if the default state of the form is always constant. (They could be implemented in another way, where the form handler redirects to a different page, but this seems somewhat messy, even if the page definition scripts themselves are neat through Unit reuse.)
+
+Separating forms and actions is not suitable for all forms, as it complicates the error case: When the user enteres bad information, the form is returned with the data the user entered, plus visual markers on the fields which caused the form to be rejected.
+
+As there can be more than one form per page, it is necessary to determine which form was submitted. In each form, there is an additional hidden variable, '<ID>_s', and the automatically generated scripts use this to select the form handler to call.
+
+
+SUBTITLE Form structure
+
+The Form Unit acts as the parent for other classes which generate the actual form HTML. When writing code and HTML, it outputs sub-units in alphabetic order (strictly perl sort() order), without any adornment. This allows complete flexibility in how forms are laid out.
+
+A single form data object is written, which contains converted data members for all the fields, and appropraite accessor functions. This class also has a member function which reads the data from the HTTP request.
+
+Typically, fields will live in a table to line them up nicely. Instances of a sub-class of TableContainer, FormTableContainer, can be created automatically with the make_container() methods. This Unit has various utility methods for for creating common field types -- but these don't have to be used. Adding FormItem units manually is just as good.
+
+
+SUBTITLE Accessing form data
+
+A data class is generated, called <AppName>Form<FormName>, where FormName has the first letter capitalised.
+
+Each form item has a name. If this was Item, the data class object would have a member variable mItem, and a GetItem() accessor function.
+
+Page variables are registered under the form name, so if the form is named 'input' and a field is 'Item', it can be used anywhere which acccepts a page variable as 'input.Item'.
+
+
+SUBTITLE Validation
+
+Each member variable of the form object has a corresponding ValidityError variable (unless the FormItem has no validation), and a NameValid() function to determine whether or not it was valid. In general, the FormItems can write all the validation code necessary, and output appropriate error messages.
+
+ValidityErrors allow for a validation failure to output different errors in different circumstances. Most will only have one error, though.
+
+To add extra validation for when the standard validation doesn't quite do enough, set the FormValidation variable. If set to none, then nothing extra is done. If set to simple or errorgen, you will need to implement an extra Validate() function for the data object class. This is free to set any of the ValidityError variables as required.
+
+If FormValidation is errorgen, then the Validate() function can also output additional error messages, for example, when an error affects more than one field. Use AddError(const char *) to add an error message. To access the translated string table for the current language and current page, call GetTranslatedStrings() (see Translations.txt for more details.)
+
+If the validation function requires access to other objects, for example, the Application object to access to the database connection, then it can specify arguments using the ArgsToValidate attribute of the Form object. This is a space separated list (or anon array) of objects, currently Application, Request and Response, or any page variable. They are passed in order as references of the appropraite type. See the function prototype generated by the Unit. Note that the use of the '=type name' notation for page variables allows you to pass in anything, but you must use the anon array method. For example
+
+	'ArgsToValidate' => ['Application', '=std::string localVar']
+
+If the validation function wishes to make available by-products of the validation process to the rest of the page, use WebAppFramework::Unit::FormItem::ExtraDataMember to create an extra member variable. (see FormItems.txt)
+
+
+SUBTITLE Conditional items
+
+Form items (and units containing items as subunits) can be made conditional at runtime on C++ expressions. This allows the same form code to be used for two purposes, or adapt a form's content at runtime.
+
+Different conditions can be used for different form items.
+
+After the form is completely assembled, the conditional_items() should be called. This takes a C++ expression, followed by pairs of items and their replacement units (or text for translation). For example:
+
+	$optform->conditional_items('params.GetFormShow() != 1',
+		'B' => undef,
+		$unit_reference => 'not shown');
+
+If the first in the pair is a string, then it refers to an item. If it is a reference, then it is a reference to any unit in the heirarchy below the form. All form items within it will be made optional.
+
+This function modifies the heirarchy, replacing the fields with appropraite OutputIf units. It also modifies the validation code to only require the fields if they are shown.
+
+The C++ expression is only executed once.
+
+This mechanism could be subverted to output different fields for different languages by having the expression dependent on language.
+
+
+SUBTITLE Handling forms
+
+When the form is submitted and all validation has been run, code can be written to perform an action. This is run in the "preparation" phase, so the standard process and redirect method can be used.
+
+To add code to be run, use the HandleSubmission attribute. This can either be a string to output as code, or a reference to a function which will write it. This will be called with parameters (ref to page, ref to form, output), and is free to output code or text.
+
+To perform a redirect, use make_redirect_code(link) on a Unit object. This will return a string containing the necessary code, which will only work in a handler function before the output has started being generated.
+
+
+SUBTITLE Form Unit
+
+This is a subclass of FragmentTemplate. The following atttributes are accepted:
+
+FormName - name of form (conventionally beginning with a lower case letter)
+FormID - one letter ID (only necessary if target is not this page)
+ExternalErrors - if present, then errors are displayed external to the form
+HandleSubmission - code to handle a validated submission, or ref to function to write it.
+FormValidation - style of validation: none, simple, errorgen
+ArgsToValidate - arguments the validation function expects (see above)
+PostSetAndValidateCode - optional, code which is output just after the form object is read and validated.
+ErrorDisplayCondition - C++ condition for displaying the error display box (the one by default above the form) If 'false', the code is not output.
+
+As a FragmentTemplate, it expects the following fragments
+
+* ErrorMarker -- displayed after a field if it wasn't a valid response. If the string is empty (no text within markers in template file), then no error markers will be displayed (no code for this generated)
+
+* InlineErrorStart -- if inline errors are used, the HTML to display before the text message.
+
+* InlineErrorEnd -- if inline errors are used, the HTML to display after the text error message.
+
+* ErrorStart -- used for both inline and external error displays, as the start of the error message.
+
+* ErrorListStart, ErrorListSeparate, ErrorListEnd -- used to separate the list of items when displaying errors externally.
+
+
+These fragments are largely just required for defaults when classes further down the heirarchy can't provide them. Generally just use the same Template and fragment names as FormTableContainer in set_defaults().
+
+
+SUBTITLE Error display
+
+The form will, by default, display an error message just before the <form> tag. This may not be quite what is required.
+
+It can be placed anywhere on the page by using the FormErrorDisplay unit. Create one like this
+
+  WebAppFramework::Unit::FormErrorDisplay->new('Form' => $form)
+
+where $form is a reference to the form unit, and then add it normally anywhere in the page.
+
+
+SUBTITLE FormItem Unit
+
+Fields are units too, and could potentially emit multiple HTML form inputs. See also FormItems.txt, for a list and details of implemented fields, and WritingFormItems.txt for details on how to implement fields.
+
+Every field must be derived from FormField, and expects at least the following attributes:
+
+Name - Form item name
+
+
+Attributes relating to validation are:
+
+Label - Label, subject to translation. Used for automatically generating error messages
+Validation - Validation information
+ValidationFailMsg - Text to display when validation fails
+
+
+Additionally, some fields may support these attributes 
+
+Attributes - Extra tags for items, eg 'class="css_style"'. Will be applied to the main tags for the form elements.
+
+
+
+SUBTILE FormTableContainer Unit
+
+This is a subclass of FragmentTemplate. It expects the following fragments
+
+* ErrorMarker -- displayed after a field if it wasn't a valid response
+
+It provides the following utility functions:
+
+add_text_field(field_name, label, validation, @specification)
+	- if label is the empty string, no label is set
+	- if validation is the empty string, no validation is set
+
+add_submit_button(field_name, button_text, @specification)
+
+add_checkbox(field_name, label, @specification)
+
+add_number_field(field_name, label, validation, default_number, blank_value, @specification)
+	- default_number should only be displayed when the number is outside the normal int32_t range
+	- if blank_value ne '', then blank entries are allowed, and the field will be set to this value
+
+add_choice(field_name, label, choices/ref to data source, style, validation, default, @specification)
+	- default not included if it is ''.
+	(see form items for more info)
+
+add_item(field_name, label, unit)
+	- adds an arbitary unit as a field
+

Added: box/features/codeforintegration/docs/webappframework/web_application/GeneratedHandlers.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/GeneratedHandlers.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/GeneratedHandlers.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,32 @@
+TITLE Generated Handlers
+
+This document describes how the generated handler functions work.
+
+
+SUBTITLE Request process
+
+The generated mini-HTTP server uses the lib/httpserver library to handle incoming requests. This calls the Handle function with references to request and response objects.
+
+The URL is decoded, fairly strictly. The URL specifies the language and page. These are dispatched using a long switch statement, with language and page names converted to 32 bit integers.
+
+The per page per language handler function then:
+
+* Decodes the paramters into a parameter data class named 'params'
+
+* Does any necessary processing, for example, reading and validating forms, and checking security. This is done first, so redirects to other pages can be done easily without having to discard any HTML generated.
+
+* HTML generation, where the HTML to send to the client is written into the Response object. Static HTML is collected into strings, which are written in one chunk.
+
+
+SUBTITLE Generation mechanism
+
+To generate all the modules, a control script scans the directory full of the page description scripts. It then runs each one in turn (via the WebAppFramework.pl script which contains all the boilerplate code), with command line arguments describing which language (or common code) to generate. Creation of a Makefile which automates this is done by the WebAppFramework.pl script.
+
+Code generation is done by repeatedly calling the write() function, once per phase. Phases are defined in the Unit.pm file.
+
+There is the facility for sub-phases. If a Unit must repeat output in a phase, for example, if it is doing a conditional on some part of the block, then it will call the write() function multiple times, each with a different sub-phase number.
+
+The special phase, PHASE_INITIALISE, is called just after everything else has been set up, with a undefined output object. One time initialisation should be done in this phase.
+
+The write function will recusively call the write() function of all the Units below it in the heirarchy.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/GettingStarted.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/GettingStarted.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/GettingStarted.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,24 @@
+TITLE Getting started
+
+There's a bit of boilerplate code which needs to be written to start an application. This needs to be customised slightly to the names of the web application. A script is provided to do the hard work.
+
+Extract the archive to get a directory like 'boxwaf-0.00'. Then
+
+cd boxwaf-0.00
+lib/webappframework/genstarterapp.pl
+
+and answer the questions. Then follow the instructions for building and running the application which the script will emit.
+
+
+SUBTITLE Example
+
+The web application in test/webappframework illustrates the use of all the Units documented. To build and try this, do
+
+cd test/webappframework
+make
+cd ../../debug/test/webappframework
+./t
+
+and then follow the instructions for running the test application. This has two "languages", one which is normal english, and the other where everything is in captial letters.
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/ImplementingTemplates.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/ImplementingTemplates.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/ImplementingTemplates.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,33 @@
+SUBTITLE Implementing new template Units
+
+This document describes how to use the template base classes to implement new types of templates, all of which read the HTML used from the templates files using the standard language based naming conventions.
+
+
+SUBTITLE Templated
+
+Parameters: Template => base name of template filename
+
+This base class simply loads the named template, and makes it available to the derived class for extracting the required text.
+
+Just before using the template, call ensure_template_loaded(output, phase). This will load the template if it has not already been loaded, taking into account the current language. (ie the template will be reloaded if the language has changed, that is, everything happens as expected.)
+
+This will call process_template(ref_to_file_text), with a reference to the entire text of the file in one single scalar. The derived class should do whatever processing is required to store fragments of this text.
+
+See PageTemplate for an example of usage.
+
+
+SUBTITLE FragmentsTemplate
+
+Parameters: Template => name of template to use
+	FragmentsName => name of fragments to pull out of the file
+
+This is a base class for generic fragment based templates -- templates which build complex HTML structures from small fragements of HTML.
+
+It is derived from Templated, and implements the process_template() method. The derived class should implement get_required_fragments(), which should return an array containing a list of all the named fragements which are required by the Unit. An error will be thown if any of these is not present.
+
+The fragments are stored in a hash array, and a references to this hash array stored in $$self{'_fragments'}.
+
+The utility method write_fragment_text(output, phase, fragname) writes the contents of the fragment as HTML to the output object if the phase is PHASE_LANG_CPP_HANDLE_OUTPUT. This can be used to write the fragment HTML without worrying about the correct phase, simplifying the code.
+
+See TableContainer for an example of usage.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/Locale.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Locale.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Locale.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,30 @@
+TITLE Locale support
+
+The framework provides basic locale support, implemented through compile time perl modules and runtime C++ classes.
+
+The perl modules provide translations of basic phrases, such as time and date related words, and orderings for form elements (eg dates).
+
+The C++ classes provide runtime access to lists of localised text (for example, month and day names) and format data to local specifications.
+
+Each page handler has a "locale" local variable, which is a WAFLocale derived class for the current language. This can be used by code, or passed to functions called.
+
+
+SUBTITLE Locale selection
+
+The framework attempts to select a suitable locale object based on the language identified passed to $webapp->add_language(). If this doesn't work (because there isn't a default language module of that name) then create one explicitly and pass it in, for example,
+
+  $webapp->add_language('test', WebAppFramework::Locale::en->new());
+
+
+SUBTITLE Creating a new locale
+
+This involves creating a new perl module under WebAppFramework::Locale, and implementing a C++ class derived from WAFLocale. See the WebAppFramework::Locale.pm module for more information.
+
+
+SUBTITLE Date specification
+
+A date specification is a list of items. The first is the type, and the following are the paramters.
+
+'ymd' -- followed by three page variable for the year, month and day respectively.
+
+'WAFFormItemDate' -- followed by a page variable referring to a field of type WAFFormItemDate

Added: box/features/codeforintegration/docs/webappframework/web_application/Localised.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Localised.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Localised.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,27 @@
+TITLE Localised data units
+
+Units are provided to display data in formats appropraite to the locale.
+
+
+SUBTITLE WebAppFramework::Unit::Localised::Date
+
+Displays a localised date.
+
+Attributes:
+
+Date => standard date specification
+Style => Long or Short (defaults to Long)
+
+See Locale.txt for the specification of the standard date specifier.
+
+
+SUBTITLE WebAppFramework::Unit::Localised::DateTime
+
+Slightly different to the above. Displays a localised date and time from a single integer variable.
+
+Attributes:
+
+DateTime => Page variable
+InFormat => YYYYMMDD or UNIXEpoch (defaults to UNIXEpoch if not specified)
+Style => Any format specified in WAFLocale.h (defaults to DateTimeFormatLong if not specified)
+

Added: box/features/codeforintegration/docs/webappframework/web_application/NotesForTranslators.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/NotesForTranslators.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/NotesForTranslators.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,115 @@
+Notes for Translators
+=====================
+
+IMPORTANT NOTE.
+
+Read all this file before commencing work.
+
+You must work on all files with a text editor which supports editing files in the UTF-8 character set. If you are not sure whether your software is suitable, please check before commencing work. Translations performed in other character sets are often useless.
+
+Make sure your software is actually a text editor. Software like Microsoft Word is not a text editor. Text editors do not offer the ability to style text (for example, making text bold).
+
+It cannot be stressed enough that you must edit the files in a suitable text editor!
+
+Because your translations are processed by a computer program, there are rigid rules on what you can and cannot do. Please follow them exactly to avoid wasting work.
+
+If you are not sure about something, ask.
+
+Please translate a few items, and send them back for checking before doing all the work, just in case!
+
+
+HTML FILES
+
+Some of the text you are asked to translate will be in the form of HTML files. Please edit the files in the UTF-8 charset, and maintain the formatting whereever possible.
+
+
+LANGUAGE FILES
+
+Most of the text is in a text file, formatted to make it easy to translate. An example entry looks like this
+
+========
+# 15505f2831f5703de81dcb263d1d7b23-h
+@ Main RequestInfo AnotherPage
+> The rain in Spain falls
+> mainly on the plain.
+
+The rain in Spain falls
+mainly on the plain.
+
+========
+
+In order, the lines are
+
+Text identifier (line begins #)
+Pages on which the text appears (line begins @)
+Original text (lines beginning >)
+
+The rest of the text is original text repeated. This is the text you need to edit.
+
+Do not change any lines beginning with #, @ or > . If you change these lines, your language file will not work.
+
+The original text is provided to help you. Do not change it!
+
+The list of pages the text appears on is provided to help you see the text in context, if needed. Do not change the list.
+
+Leave a blank line above and below your translated text. It doesn't matter how many lines your translation takes up.
+
+
+As an example of what is required, the above entry might be translated as
+
+========
+# 15505f2831f5703de81dcb263d1d7b23-h
+@ Main RequestInfo AnotherPage
+> The rain in Spain falls
+> mainly on the plain.
+
+La lluvia en
+Espana cae principalmente
+en el llano.
+
+========
+
+
+COMPUTER READABLE MARKERS
+
+Within some of the strings, you may find computer readable markers and HTML markup. You MUST NOT translate these markers.
+
+HTML is entities within angle brackets, for example, <b>.
+
+The markers are entities within curly brackets, for example, {name.Other}.
+
+HTML and the markers should be left untranslated, and with their brackets intact.
+
+For example, consider this
+
+========
+# with_markup-h
+@ RequestInfo
+> This is a sentence with some <b>HTML markup.</b>
+> This is a link {['Name', 'Param' => 'Value']}. This is a marker to
+> show where to insert from data from the system: {widgets.Item},
+> and this is another: {=std::string userID}.
+
+This is a sentence with some <b>HTML markup.</b>
+This is a link {['Name', 'Param' => 'Value']}. This is a marker to
+show where to insert from data from the system: {widgets.Item},
+and this is another: {=std::string userID}.
+
+========
+
+Within this example, the following strings must not be translated:
+
+  <b>
+  </b>
+  {['Name', 'Param' => 'Value']}
+  {widgets.Item}
+  {=std::string userID}
+
+
+
+
+Thank you for your tolerance!
+
+
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/OtherUnits.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/OtherUnits.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/OtherUnits.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,120 @@
+TITLE Other Units
+
+Details of other Units provided.
+
+SUBTITILE TranslatedText
+
+Parameters: Text => text to output
+
+The text is in the default language, and will be subject to translation when output in other languages.
+
+Usually added implicitly with the Unit::add_text() method.
+
+The string is translated, then subject to interpolation. To include the runtime value of page variables, enclose the name in {} braces. For example,
+
+  $unit->add_text('position', 'Hello {params.Username}!');
+
+would include the runtime value of this variable in the text. Note you can use the = notation to include local variables. Translations can add new variables, change the order, and remove variables.
+
+
+SUBTITLE RawHTML
+
+Parameters: HTML => raw HTML to output
+
+The HTML is output without any translation.
+
+
+SUBTITLE LinkToPage
+
+Parameters: Link => link specification 
+   * => any other attributes which should be output
+
+Unit to output a <a href="" ...> link. Doesn't output a closing link (use RawHTML). Often added implicitly with the Unit::link_to() method.
+
+The link is specified as an array ref to the link specificaiton parameters, ie [$pagename, @parameters].
+
+Other attributes are written as attributes in the <a> HTML tag, for example, 'class' => 'large' will outputs class="large" as a tag attribute, applying a CSS class to that link.
+
+
+SUBTITLE ListOfLinks
+
+Parameters:
+Links: Array of link, as [['Translate this', [linkspec]], ...]
+Separator: raw HTML separator, if ' ' not acceptable (optional)
+* => any other attributes which should be output
+	(eg 'class' => 'large' for CSS class, outputs class="large" as attribute)
+
+This will output a simple set of links, with the links names translated. Especially useful for action links in the display of database queries.
+
+
+SUBTITLE Menu
+
+Parameters:
+	Items => array of items, each of which is anonymous array ['Name (translated)', [link spec], 'optional C++ condition for addition "this page" check']
+	DifferentOnThisPage => if set, use the alternative ItemThisPage fragment when we're on the page we're linking to
+
+
+This is a FragmentsTemplate based unit. Required fragements are:
+
+Begin - top of menu
+Item - item template, when link isn't to the current page
+ItemThisPage - item template, when link is to current page
+End - bottom of menu
+
+For each entry in the menu, either Item or ItemThisPage will be choosen. Within that fragement, [URL] will be replaced by a link to that page, and [TEXT] with the name of the item (after translation).
+
+The Item/ItemThisPage separation is to allow different representations of the current page within the menu (for example, not including a link to it) when the DontLinkOnPage parameter is set.
+
+
+SUBTITLE Variable
+
+Parameters: Variable => name of variable
+
+This Unit writes the contents of a named variable to the HTML output, in defanged form.
+
+
+SUBTITLE ChoiceLookup
+
+Parameters: Variable => name of variable
+	Choices => list for translation, | separated
+
+This unit displays one of the options from the Choices list (specified as a string with | separated items) using the variable as an integer index into the list. The list of choices is translated.
+
+
+SUBTITLE ChoiceLookupList
+
+Parameters: Variable => name of variable
+	Choices => list for translation, | separated
+	Separator => raw HTML to separate items in the list (not translated)
+
+Similar to ChoiceLookup, except that the page variable is expected to be a string containing a , separated list of integers. Each integer outputs an item from the list.
+
+
+
+SUBTITLE SimpleContainer
+
+This simply outputs the sub-units in perl sort() order, optionally separating them with the raw HTML in the Separator attribute (not subject to translation).
+
+It also exports the utility function join_units(), similar to the perl join function. The first parameter is (untranslated) HTML to separate the units, then the other parameters are either units or text strings (to be translated). It returns a SimpleContainer structure with all the units added.
+
+
+SUBTITLE OutputIf
+
+Parameters: Condition => Condition to evaluate.
+
+The condition is C++ code evaluating to true or false, for example, "WAF::Var('form.IntegerValue') == 3". Units are placed at positions 'true' and 'false', and the appropraite Unit is output when the expression is evaluated at runtime.
+
+Note that it is only the HTML output phase which is conditional. Code in all other phases will still be written (and excecuted) regardless of the conditional.
+
+
+SUBTITLE IncludeOnPages
+
+Parameters:
+	Pages => list of pages the sub units should be included on
+	NotPages => list of pages the sub units should not be included on
+(one or the other can be used, but not both)
+
+This Unit only outputs it's sub units on specific pages. On other pages, the sub-units are actually deleted.
+
+It's useful for including in the setup_page function where some units should only be used on a specific subset of pages.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/StaticFiles.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/StaticFiles.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/StaticFiles.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,19 @@
+TITLE Static files
+
+Most applications require some static files, for example images and possibly the home page. Ideally these should be served from a separate web server and integrated using the front end reverse proxy, but this is unhelpful for testing and means that the application executable is not everything which is required for deployment.
+
+To that end, a facility for including static files is included.
+
+In the bin/webapp directory, create one or more directories containing static files. Then, in the main web application .pl file, include lines like
+
+  $webapp->add_static_directory('Static', 'static');
+
+The first parameter is the name of the directory, and the second the name of the directory on the web server under which they should appear.
+
+If the second name is null, then they appear in the root directory. To set the home page, use the $webapp->set_homepage() function. See WebAppStructure.txt.
+
+The framework knows the mime type for some common file extensions, and if an extension is unknown an error will result. To add a new type mapping, use
+
+  $webapp->add_file_ext_to_mime_type('ext', 'mime/type');
+
+Once files have been added using these two functions, they can be using in the pages using absolute paths.

Added: box/features/codeforintegration/docs/webappframework/web_application/TemplateUnits.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/TemplateUnits.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/TemplateUnits.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,64 @@
+TITLE Using the template Units
+
+A number of template Units are implemented. This document describes how to use them, and another document describes how to produce new templated Units using the base classes.
+
+The intention is for a single template document to be able to template all the HTML for the entire application, and be able to viewed in a web browser to see a reasonable rendition of the look and feel. The designer simply uses text and HTML comment markers to describe how the template should be used.
+
+
+SUBTITLE Template files
+
+Template files are stored within a 'Templates' subdirectory, and are named name.language.html.
+
+The name of the template is given as simply 'name' to the template classes. If a template exists for the specific language, that template will be used, otherwise the template for the default language will be used.
+
+
+SUBTITLE PageTemplate
+
+Parameters: Template => name of template to use
+
+This Unit inserts sub-Units into a HTML page at specific markers. Markers are specified as ###name###, which will be replaced with the Unit at position 'name'.
+
+To allow the same template to be used for FragmentsTemplate based templates, HTML within the comment markers <!--PageTemplate-Omit-Begin--> and <!--PageTemplate-Omit-End--> is ignored.
+
+
+SUBTITLE SectionTemplate
+
+Parameters: Template => name of template to use
+	Marker => Marker used to delimit HTML snippet in template
+
+This is very similar to the PageTemplate unit, except that instead of taking the entire file and snipping out the middle, it takes a chunk from the middle delimited by HTML comments.
+
+For example, you might set the Marker parameter to Section1, and then in your template file have something like
+
+<!--Section1-->
+	html
+	###insertPoint###
+	more html
+<!--Section1-->
+
+in the middle of the file. This is then extracted, and units inserted at the insertation points.
+
+
+SUBTITLE FragmentsTemplate
+
+Parameters: Template => name of template to use
+	FragmentsName => name of fragments to pull out of the file
+
+This is a base class for generic fragment based templates -- templates which build complex HTML structures from small fragements of HTML.
+
+Within the HTML file, fragments are marked within <!--name-fragname--> <!--name/--> where 'name' is the name specified in the FragmentsName unit attribute, and 'fragname' is the name of the fragment. The fragments required depend on the derived class.
+
+
+SUBTITLE TableContainer
+
+Parameters: Template => name of template to use
+	FragmentsName => name of fragments to pull out of the file
+
+Derived from FragmentsTemplate. It requires fragments named
+
+  Begin RowBegin CellBegin CellEnd RowEnd EmptyCell End
+
+to be present in the template. From these, it builds HTML table structures, with sub-units positioned in the cells. The position of a cell is specified by the named position as 'x_y'. So position 2_1 is column 2, row 1, with columns and rows numbered from 0.
+
+You can specify more than one type of cell, by adding CellBeing-X and CellEnd-X fragements, and adding the type to the position, eg x_y_X, where X is any alphanumeric string.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/Translations.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Translations.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Translations.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,119 @@
+TITLE Translations
+
+Multiple versions of the same page can be produced for internationalisable applications. The language is choosen by including the language name in the URL of the page.
+
+There is an additional documentation file, NotesForTranslators.txt, which is aimed at helping a translator work on the language files.
+
+
+SUBTITLE Templates
+
+Template files are used as the source of HTML for several Units. Templates are named "name.language.html", so to translate, simply author additional templates.
+
+Alternatively, surround strings in the file with <!--T--> markers, and the text between them will be translated through the translation system.
+
+
+SUBTITLE Strings
+
+All other strings used should be passed through the translation system. The intention is for the application to be written in the default language. Additional languages are written by translating these strings using a lookup table read from a file.
+
+Strings starting with *key* have that prefix removed, and the 'key' used as the key for looking up translated strings.
+
+
+SUBTITLE Translation files
+
+Each language has a single translation file in the Languages directory. Each translated string has an entry, which looks like this:
+
+=========
+# Key
+@ Page list
+> Original text
+
+Translated text
+
+=========
+
+The key is either
+
+* A user supplied key
+* The text, with whitespace collapsed and turned into _
+* A hash of the text
+
+This must never be edited -- it is used to look up strings. Editing the key will result in duplicate strings being added.
+
+The line beginning with @ is a list of all the pages the string is found on. This is maintained, so is up to date at the end of every run.
+
+Lines beginning with > are the original text, and should be never edited. It will be updated on every run.
+
+The translated text can be freely edited, even in the default language, although this is not advisable.
+
+
+SUBTITLE User supplied keys
+
+If you start some translated text with *key*, then 'key' is used as the key for the text. Even if you modify the text, a new entry will not be created, and translations will be tracked using this key.
+
+This is useful when you have text which might be edited in the default language in the pages themselves.
+
+If the text is more than one or two words long or might be edited in the future, use a user supplied key!
+
+
+SUBTITLE How to translate an application
+
+Take the corresponding language file, and translates all the strings. Do not alter the Key or Page list -- this will break the translation system.
+
+The language file can be returned to the directory even if more strings have been added to the application. Additional strings will be added when the application is compiled again.
+
+WARNING: The default make system does not rebuild pages when the language file changes. This is because they are overwritten each time the application is regenerated, and would result in every file being rebuild every time the application was made.
+
+
+SUBTITLE Using translated strings
+
+The output object passed to each Unit's write() method maintains translations. Any string which will be shown to the user (and wasn't sourced from a template file) should be translated through this object.
+
+Call write_text_translated(text) to write text which is to be translated.
+
+Use translate_text(text) to obtain a translated text string (without writing it).
+
+
+SUBTITLE Using translated strings in code not in the main handler cpp file
+
+It's slightly more tricky to translate strings which aren't in the main handler file for that language. A table of strings can be created, which can then be passed to functions called from the handler function.
+
+Call add_translated_string(name, string) on any Unit. This will add a string to be translated to the table, and add a #define to the page's .h file as the index of the string in the table.
+
+The table has the name PageTranslatedStrings (a static const char **), and #defines are named
+
+	PAGE_TRANSLATED_STRING_name
+
+where PAGE is the name of the page in upper case, and name is the name of the string given in the add method.
+
+
+SUBTITLE Varying the pages on a per language basis.
+
+Some translations will require a little more than just translating strings. The $language variable is set to the current language, so the page definition script can do differnet things in page scripts or units to reflect different layouts for different languages.
+
+Restrictions:
+
+* Every language must have the same set of parameters
+
+* Every form must have exactly the same set field names (all spelled the same, all present)
+
+
+SUBTITLE Strategies
+
+* A form must be more different than simply being rearranged
+
+Use two pages, and on the pages which link to them, use a different page name depending on the $language variable.
+
+
+* One default language string maps to several other strings in another language
+
+For example, a string may need to be subtly different depending on the context in a different language.
+
+In the page description scripts, prefix the string with some identified, for example:
+
+	"Use 1: My lovely string"
+	"Use 2: My lovely string"
+
+Then, in the default.txt file, edit the new entries so that the text is just "My lovely string" in both cases. In the other languages, edit appropraitly.
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/Units.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Units.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Units.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,156 @@
+TITLE Units
+
+Various Units are defined, and an author should expect to implement a few Units when developing their application.
+
+
+SUBTITLE Common functionality
+
+The Unit base class implements most of the required functionality. In many cases only a write_unit() function will need to be implemented.
+
+
+* Construction and attributes
+
+The default new() operator takes a hash array style set of named attributes. It is intended that this should, in general, be the only necessary configuration.
+
+Attributes can be changed or added later using the set(attribute, value) method.
+
+
+* Default attributes
+
+Every Unit has defaults. Since the parameters are effectively a hash array, the set_defaults() method sets a default set of parameters, and actual arguments to the new() method override these.
+
+The set_defaults is generally called in the application objects setup_page() method, to set up look and feel type options.
+
+
+* Unit heirarchy building
+
+add_pre_unit(unit) adds a Unit to the pre units list (see overview)
+
+add_post_unit(unit) adds a Unit to the post units list
+
+add_unit(position, unit) adds a Unit in the named position.
+
+When implementing another Unit, it should only be necessary consider the positioned Units, as the default write() method will handle pre and post Units in the correct order.
+
+Units can also be added at positions in the new() operator by delaring an attribute of '@position', for example,
+
+WebAppFramework::Unit::OutputIf->new('Condition' => "WAF::Var('Security.IsAuthenticated')",
+		'@true' => WebAppFramework::Unit::Variable->new('Variable' => 'Security.Username'),
+		'@false' => WebAppFramework::Unit::TranslatedText->new('Text' => '(no user logged in)'));
+
+creates an OutputIf unit with a Variable unit at the 'true' position and a TranslatedText unit at the 'false' position.
+
+
+* Finding a unit within a heirarchy
+
+It's sometimes necessary to find a unit within a heirarchy, for example, to add or modify units with the default page. On a unit, use the find_unit method to search for units below it. (Not including that unit itself.)
+
+Parameters are: partial type, parameter specifications. The type is matched so that the right most part of the type matches the string.
+
+For example, $page->find_unit('Form', 'Name' => 'login') will find the Form Unit with the name 'login'.
+
+
+* Helper functions
+
+add_text(position, text) adds translated text in the specified position. Returns the text Unit added. You can interpolate the value of page variables by enclosing them in {} braces. For example,
+
+  $unit->add_text('position', 'Hello {params.Username}!');
+
+would include the runtime value of this variable in the text. This interpolation is done by the TranslatedText unit.
+
+link_to(link_specification) surrounds the Unit with a <a href=""></a> HTML link as a matched pair of pre and post Units. Returns the link object, so additional attributes can be set. Note that you can use {[link_spec]} notation to include raw links in the HTML generated, avoiding clumsy use of the link_to function.
+
+
+SUBTITLE Producing output from a Unit
+
+To generate the code, the write(output, phase) method is called repeatedly, once per output phase. Each output phase corresponds to a section in the output files, for example function declarations, page handling, and HTML output. Refer to the constants at the top of Unit.pm for exact details.
+
+An Output object is used to mediate the writing of code and HTML. In the case of HTML, the output is collected together, and only written in the largest possible chunk for efficiency. write_code(text) writes code, unchunked, and write_text(text) writes HTML.
+
+All Output should be written via this object to avoid buffered output causing problems with incorrectly ordered HTML output.
+
+The base class implements a write() function, which calls write_unit(output, phase) to perform the Unit specific functions.
+
+
+SUBTITLE Variables
+
+To retrieve an expression which evaluates to a variable, use get_variable(var_name). This returns a CppVariable containing the type and name of the expression (or will error and terminate the compilation if the variable can't be found). This can then be used, perhaps converting it to the required type.
+
+If a CppVariable is passed in, then it will be returned unmodified. This allows you to use CppVariable objects anywhere where variable names are expected.
+
+If a string is passed in which starts with a =, then the rest of the string is parsed as a CppVariable specification string, and the result returned. For example, if you have a local variable declared as
+
+  int32_t someValue;
+
+then you can use it anywhere which expects a page variable name by using the string '=int32_t someValue'.
+
+If the string is enclosed in [], then is evaluated as a link specification, and returned as an expression evaluating to a std::string containing the link. For example
+
+  ['Page', 'Param' => 'CONSTANT:2']
+
+Note that in some contexts, such as outputing translated text with interpolated values, the output is optimised to write constant text whereever possible. The resulting HTML generated when the page is requested is the same in every case.
+
+(You can pass in a CppVariable or a string of the '=type name' form to anything which expectes a page variable name, for example, in link specifications and interpolated text.)
+
+The root Unit maintains a list of all the Variable namespaces. register_variable_namespace(name, array_ref) registers a reference to an array of CppVariables describing all the members of that namespace.
+
+
+SUBTITLE Generating links to other pages
+
+Each page has a number of parameters. URI's to pages which specify all these parameters are called page addresses. The Unit base class provides the necessary functionality to generate these links.
+
+A page address is specified in function arguments as
+
+(PageName, ParameterName => Value, ParameterName2 => Value2, ...)
+
+The values can be a Variable name (see concepts section in main document) or a constant, prefixed with CONSTANT:, eg 'CONSTANT:42' for a constant number or string.
+
+To use an arbitary local variable in the link, use 'LOCAL:type name' to specify the variable and it's type.
+
+Not all parameters need to be specified. If a parameter is not specified, the following rules are used to find a value:
+
+- if the link is to the current page, the current value (at runtime) of that parameter on this page
+
+- if the parameter is a global parameter, the current value of that global parameter
+
+- the default value set when the parameter was declared
+
+If none of these produce a value, then an error will be thrown at code generation time.
+
+To write into the HTML output, call write_page_address(output, specificartion). write_page_address_this(output, partial_specifiction) is similar, but omits the page name from the specification, and links to the current page.
+
+To obtain an expression which evaluates to a std::string containing the URL, use generate_page_address_expression(specification), but note that this can only be used after the Unit heirarchy is complete, and everything has been initialised and registered all the namespace variables.
+
+If you are generating code to output via an Output object, you could also use link_spec_to_WAF_code(link_spec) to return a 'WAF::Link()' psuedo-function call. The link specification can be passed in as a plain set of arguments, or as a reference to an anonymous array.
+
+
+SUBTITLE Redirect code
+
+make_redirect_code(link_specification) will return code which will return a redirect to the given link to the user's browser. This code can only be used in the preparation phase of a handler, before the output has started being generated.
+
+
+SUBTITLE Headers
+
+The code created by Units may require extra headers to be included in the .cpp file. Implement get_required_headers_this(type) to add new headers. Type is HEADERS_SYSTEM or HEADERS_PROJECT for system and project headers respectively.
+
+(This is not implemented as a writing phase, as it would probably lead to duplicated #include directives.)
+
+
+SUBTITLE Global file and detection of default page
+
+There are phases for writing to a global file. This contains code for units which are used on many pages.
+
+A unit can tell whether it is part of the basic page (created in the setup_page() subroutine) or inserted in a page file since in_default_page() is called for every Unit in this default page before any write_unit() subroutines are called.
+
+Units can only write to the global files if they are created in setup_page().
+
+
+SUBTITLE Other Unit functionality
+
+get_webapp() -- return the web application object
+
+get_root() -- return the root Unit, which contains all other Units. (This Unit contains additonal data, such as the list of parameters for the page.)
+
+get_pagename() -- return the name of the page
+
+


Property changes on: box/features/codeforintegration/docs/webappframework/web_application/Units.txt
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/docs/webappframework/web_application/Validators.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/Validators.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/Validators.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,28 @@
+TITLE Validators
+
+Validators are perl modules which write code to validate variables against a preset criteria. The main use is for extending the validation capabilities of the TextField form item.
+
+The built-in validators are referenced by strings of the form name(args) and the (args) can be omitted if they are to be the default, or the validator takes no arguments.
+
+Additional validators can be written by users. Anywhere which accepts a validator string will accept a reference to a validator. Dervive the module from WebAppFramework::Validator, and see the comments in the .pm file for exactly what needs to be provided.
+
+Note that validators are allowed to modify the variable they validate, for example, to move it to a canonical form.
+
+
+SUBTITLE email address Validator
+
+The WebAppFramework::Validator::email module validates a string for being a valid email address. If it has arguments 'no-lookup', then the domain name is not looked up in DNS. This is useful for login forms where users might need to enter an invalid email address to regain access to an account.
+
+In a TextField, specify it as 'email' or 'email(no-lookup)'.
+
+Use requires lib/smtpclient to be included in the depenencies for the app in modules.txt.
+
+
+SUBTITLE phone number Validator
+
+The WebAppFramework::Validator::phone module validates a string for being a valid phone number. This means it contains only whitespace, digits, '+', '.' and ','.
+
+The canonical form has leading and trailing whitespace removed, and whitespace within collapsed to a single space.
+
+In a TextField, specify validation as 'phone'.
+

Added: box/features/codeforintegration/docs/webappframework/web_application/WebAppStructure.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/WebAppStructure.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/WebAppStructure.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,84 @@
+TITLE Web application structure
+
+This document describes how the files within the application's source directory are structured.
+
+
+SUBTITLE Application description file
+
+This file is named after the application name with a .pl extension. It lives at the root level. It's simply a perl script fragment. The $webapp variable is a WebAppFramework object, which should be initialised for the web application. This includes
+
+* Setting up languages
+
+* Defining global paramters
+
+* Defining each page, the page's short name, and the page's paramters
+
+* Implement a setup_page() method.
+
+In addition, default for various units could be set up.
+
+Each page has both a long and short name. The long name is for use within the page description scripts, and should be as descriptive as possible. The short name is used in the page URL, and should be exactly four characters long.
+
+The setup_page() method must return a Unit. This is should be set up for all the common elements of each page, mostly look and feel boilerplate, and any global code.
+
+If the application requires extra configuration directives in it's .conf file, use $webapp->add_extra_config_directive(type, name) to add them, where type is 'int' or 'string', and name is the name of the directorive.
+
+To retrieve these values in a page's handler, the utility function GetConfigurationVariable() is provided for easy access.
+
+The configuration object will be passed to the application object's ChildStart() and RequestStart() functions.
+
+
+SUBTITLE Application home page
+
+The URI for the home page (the '/' of the web server) is set by $webapp->set_homepage(type, uri)
+
+'type' is either 'rewrite', in which case the URI is rewritten behind the scenes, or 'redirect', in which case the browser is redirected and the user will see the URI displayed in the address bar.
+
+Use rewrite with caution. Links to the same page may not work if parameters are used in any way. Test carefully!
+
+
+SUBTITLE Makefile.extra
+
+This Makefile fragment runs 
+
+   ../../lib/webappframework/WebApplication.pl TestWebApp.pl make
+
+where TestWebApp.pl is the application description file.
+
+
+SUBTITLE Makefile.webapp
+
+This is automatically generated.
+
+
+SUBTITLE Template directory
+
+This contains all the template HTML files for the application.
+
+
+SUBTITLE Languages directory
+
+This contains the language files. Each is named 'language.txt' except the default language, which is 'default.txt'.
+
+
+SUBTITLE Pages directory
+
+This contains all the page description scripts. These are perl fragments. The following variables are set:
+
+$webapp -- WebAppFramework object
+
+$page -- a Unit initialised by setup_page() and the WebAppFramework object.
+
+$langauge -- the name of the current language being generated. This can be used to do different things for different languages -- but generally shouldn't need to be used unless the order of the elements of the page needs to change on a per language basis. (Text should be translated automatically.)
+
+The page description scripts are free to alter the root Unit however they want.
+
+
+SUBTITLE WebAppName.*
+
+A class with the exact name of the web application's name is expected to be implemented, and will be included as a member variable of the mini-HTTP server named mApplication.
+
+This must be derived from WebApplicationObject.
+
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application/WritingFormItems.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application/WritingFormItems.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application/WritingFormItems.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,137 @@
+TITLE Writing FormItem Units
+
+This document describes how to implement new form items, which output fields within Forms.
+
+All form items should be derived off FormItem, and various methods implemented. Most will have a default implementation which is suitable for most methods.
+
+
+SUBTITLE HTML form IDs
+
+HTML form elements require names for posting. This are refered to as IDs in this documentation. A single FormItem may use more than one ID.
+
+IDs are allocated by the Form object, and are opaque. They are not the name of the FormItem.
+
+
+SUBTITLE Acceptor code
+
+To accept data from the form, the item must write code to take a std::string and turn it into the data item. This is called acceptor code. It must also perform any validation required.
+
+
+SUBTITLE Utility methods
+
+* get_id()
+
+When a FormItem has just one ID, this function will return it.
+
+
+* get_item_name()
+
+Return the name of the item (from the Name attribute)
+
+
+* get_item_label()
+
+Return item label (pre translation), abort if not set
+
+
+* get_item_validation()
+
+Return item validation string, abort if not set
+
+
+* get_form()
+
+Return a reference to the Form object containing this item.
+
+
+* get_tag_attributes()
+
+Return a string containing any extra attributes for the class. The first character is a space, for neat additions. Or the empty string if there are none.
+
+
+* get_value_page_variable()
+
+Returns a page variable name referencing the current value entered by the user. Uses get_form_variables() to discover where it can be found -- errors if there's more than one value.
+
+
+SUBTITLE Methods to override
+
+* get_num_ids_required()
+
+Return the number of IDs required by the item. set_ids() will be called later by the Form object with a list of all required IDs.
+
+Default: return 1
+
+
+* get_form_variables()
+
+Return a list of the data members required in the data object (CppVariables, without the 'm' prefix in the name).
+
+Default: Use get_form_variable_type() to find the C++ type of the data member for the form object, and return a variable with the item's name.
+
+
+* get_form_variable_type()
+
+Return the type of data member for the form data object.
+
+No default. Only necessary if the get_form_variables() used.
+
+
+* requires_acceptor_code()
+
+Whether or not this field requires acceptor code written.
+
+Default: return true
+
+
+* write_value_acceptance_code(output, data_source, valid_boolean_name)
+
+This method writes the acceptor code to the output. The acceptor code converts and stores the string data given, and validates it.
+
+data_source is a CppVariable which describes where the value the user entered can be obtained.
+
+valid_boolean_name is the name of the variable, type bool, which should be set to true if the data for the field is valid.
+
+
+* write_validation_fail_message(output)
+
+Write the validation fail message. Text and/or straight text acceptable.
+
+Default: See if the ValidationFailMsg is set, and if so, output that (translated). Otherwise, use get_values_of_validation_fail_messages() to see if there are multiple error codes. If not, use make_validation_fail_message() to generate a string, and output that (translated). If there are multiple codes, call make_validation_fail_message() multiple times, with the argument of the error code.
+
+
+* make_validation_fail_message()
+
+Returns a generated string which describes why the field failed to pass validation -- use to automatically generate error messages. Results will be translated.
+
+
+* make_validation_fail_message()
+
+Return a list of all possible error codes. Entries may contain several values which generate the same string separated by the | character.
+
+Default: return empty list
+
+
+* write_unit()
+
+The standard write unit must be overridden. This will be called twice, with subphases OUTPUT_SUB_PHASE_DEFAULT and PHASE_LANG_CPP_HANDLE_REDISPLAY. The former is for when the object is in the default state, and the latter for when it is being redisplayed.
+
+The default state should display a default value, and the item should have some provision for stating it, if appropraite. The redisplay option must use data from the form object.
+
+
+* disable_error_marker()
+
+Whether or not the error marker should be disabled, and never displayed for this item.
+
+Default: return false
+
+
+* always_passes_validation()
+
+Whether this item always passes validation -- shortcuts writing error code, for example.
+
+Default: return false;
+
+
+
+

Added: box/features/codeforintegration/docs/webappframework/web_application.txt
===================================================================
--- box/features/codeforintegration/docs/webappframework/web_application.txt	                        (rev 0)
+++ box/features/codeforintegration/docs/webappframework/web_application.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,127 @@
+TITLE Rapid web application development
+
+A method of rapidly producing web applications, by generating code to implement the various pages on the web application.
+
+Other files document the system in detail. The following order of reading documents is recommended.
+
+This document
+WebAppStructure
+Example
+Units
+Code
+Translations
+TemplateUnits
+Forms
+OtherUnits
+ImplementingTemplates
+GeneratedHandlers
+
+
+SUBTITLE Motivation
+
+The usual web scripting languages provide a general-purpose programming syntax which happens to output web pages. This results in a lot of similar code being written for each page, for example, form handling code. This is inefficient. In addition, useful tasks such as templates to separate the presentation from the logic has to be written anew for each project, and internationlisation is difficult.
+
+There exists a number of potential solutions. However, the emphasis for these is to provide an "easy to program" way of generating web applications, without regard to the efficiency of the solution when deployed. They also have a tendancy to require a lot of additional software (such as java, large intepreter libraries) and force the author into a strict way of thinking.
+
+These systems generally have no pre-processing stage, that is, the code written by the author is run directly on the web server, and the complex handling of the user's input and page generation done dynamically, with lots of processing to essentially generate the same HTML each time the code is executed.
+
+Also, a single language is used, which has it's pros and cons, for example, easy text processing but lacking in efficiency.
+
+This system presents an alternative way of thinking, to generate a web application using a set of perl scripts which generate C++ code. The perl does all the complex generation of page elements, and produces the simpliest possible C++ code to generate the page. As much of the processing is done at this "compile" time.
+
+The C++ compiles to a simple web server. This is designed to run on a server behind another web server, for example squid or Apache with mod_proxy. The front end server handles the interactions with the actual user, and efficiently transmits the generated HTML, leaving the web application ready to generate another page.
+
+This approach allows the system to concentrate on being easy to use, rather than have to worry about efficiency of the end code. It doesn't matter if it takes a while to produce the code, but it'll run as fast as possible.
+
+
+SUBTITLE Design overview
+
+The author writes a number of 'pages'. These are snippets of perl script, which generates some C++ code to handle the page. In addition, a framework script generates a partial Makefile, and the specific code for the web server (for example, the request to handler dispatch mechanism).
+
+As way of an example, a section at the end of this document walks through the test web application.
+
+Design aims:
+
+* Separate look and feel from executable code.
+
+* Fully internationalisable, with translators not required to touch any files containing code.
+
+* Efficient standalone code written.
+
+* Possible to implement anything, but especially good for database driven web application.
+
+* Easy to add new, reusable entities
+
+* Provide a set of general purpose tools to do common things: list items from databases, display information about objects, handle forms
+
+When actions are performed as a result of form submissions, the same page handles the submission as generates the form data. Movements to other pages as a result of a submission are done through browser redirects.
+
+
+SUBTITLE Concepts
+
+* Page
+
+Single "page" of the application, for example, listing items out of a database table, or a form and the associated code to perform the required actions when the form is submitted.
+
+
+* Page definition script
+
+A snippet of perl script which uses the framework modules to write executable C++ code for the page.
+
+
+* Module
+
+The C++ code which is the result of executing a page definition script. This is the actual code which is executed on the server.
+
+This code uses a few external C++ classes, including a mini-HTTP server and associated framework. It contains the exact code necessary for the page -- all look and feel HTML is embedded, and if statements for configuration are performed in the code writing stage, not final execution.
+
+
+* Parameters
+
+The application has a small number of global variables (eg user and login information), and each page a number of arguments (on a per page basis). These parameters are passed in the URI of the current page, as
+
+/appname/language/page_name/app_parameters/page_parameters
+
+Each request must contain exactly the right number of parameters, otherwise an error is displayed.
+
+Parameters definitions contain defaults, but these are only used when generating a link to that page, not when they are missed out from the URI. This is so that relative URI's can be generated when linking to a page from itself, reducing the amount of HTML which needs to be sent and the code to generate this link. To maximum the benefit of this scheme, the ordering of parameters needs to be considered carefully, placing the most often changed at the end of the list.
+
+
+* Variables
+
+A variable is a named, strongly typed, item of data available at runtime. Each source of data is within a specific namespace, and variable are references as namespace.variable_name. Available namespaces are
+
+	- params (page parameters)
+	- formname (form variables, where formname is the name of the form)
+	- formdata (raw form data)
+	- cookie (cookies available in HTTP request)
+	- config (directives in the root of the config file)
+	- fn (local function variables)
+
+Variables are used to specify the source of data for all framework functions which require data. While they are strongly typed, in most cases automatic conversion will take place. If a variable is in an invalid format for the conversion (eg alpha characters in a string being converted to a number) an exception will be thrown at runtime.
+
+
+* Unit
+
+A Unit is a single functional unit of a page, for example, a form. It handles both display and any user initiated actions.
+
+They are implemented as perl objects which are created in the page definition script, and then used by the framework to write the C++ code which displays and performs the necessary actions.
+
+A Unit stores references to other Units, providing a heirarchy of Units. (A tree of all the Units is included in the generated C++ files to show what exactly was used!) There are three types of references stored:
+
+* pre-Unit -- these are output just before this Unit is output
+* post-Unit -- output just after this Unit is output
+* positioned -- Given a simple text string as a position name, the sub-Unit is then output at an appropraite point. This depends on the implementation of the Unit, for example, the PageTemplate Unit outputs the Unit at the corresponding marker in the HTML template.
+
+(see below for more details on forms)
+
+Units can be considered as containers which surrouding the HTML created by the contained units with other HTML to form a complete HTML page.
+
+
+* Language
+
+The framework is fully internationalisable. The pages are written in a default language, for ease of development. To produce the translated versions, all strings pass through a translator. Translation files are automatically maintained, with new strings being added ready for translation as they are first used.
+
+The $language variable is set, so the page definition script can do differnet things in page scripts or units to reflect different layouts for different languages. However, there are limitations -- for example, a form must have exactly the same fields in in all translations.
+
+


Property changes on: box/features/codeforintegration/docs/webappframework/web_application.txt
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/Database/Query.pm
===================================================================
--- box/features/codeforintegration/lib/database/Database/Query.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/database/Database/Query.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,499 @@
+package Database::Query;
+use strict;
+use vars qw/@required_attributes %allowed_attributes %allowed_flags/;
+use CppVariable;
+
+@required_attributes = qw/Name Statement/;
+%allowed_attributes = qw/Name 1 Statement 1 Parameters 1 Results 1 Flags 1 AutoIncrementValue 1/;
+%allowed_flags = qw/SingleValue 1/;
+
+sub new
+{
+	my $type = shift;
+	my $self = {@_};
+	
+	# check everything required is there
+	for(@required_attributes)
+	{
+		die "Attribute $_ not specified to Database::Query"
+			unless exists $$self{$_}
+	}
+	# check nothing bad is there
+	for(keys %$self)
+	{
+		die "Attribute $_ is not a valid attribute to Database::Query"
+			unless exists $allowed_attributes{$_}
+	}
+	# parse and check flags
+	if(exists $$self{'Flags'})
+	{
+		for(split /\s+/,$$self{'Flags'})
+		{
+			die "Flag $_ is not a valid flag for Database::Query"
+				unless exists $allowed_flags{$_};
+
+			# set flag
+			$$self{'Flag_'.$_} = 1;
+		}
+	}
+	# check name
+	unless($$self{'Name'} =~ /\A[a-zA-Z0-9_]+\Z/)
+	{
+		die "Bad name given to Database::Query, must contain only [a-zA-Z0-9_]"
+	}
+	# check autoincrement value flags
+	if(exists $$self{'AutoIncrementValue'})
+	{
+		if($$self{'AutoIncrementValue'} !~ m/\A\w+\s+\w+\Z/)
+		{
+			die "Bad AutoIncrementValue given to Database::Query, must be 'TableName ColumnName'"
+		}
+		if($$self{'Statement'} !~ m/\A\s*INSERT\s+INTO/i)
+		{
+			die "AutoIncrementValue given to Database::Query when the statement does not appear to be an insert statement"
+		}
+	}
+
+	# Basic checks pass, move on to parsing the attributes
+	my $n_params = 0;
+	if(exists $$self{'Parameters'})
+	{
+		my $params = [];
+		my $params_null = [];
+		parse_vars_list($$self{'Parameters'}, $params, $params_null, 'Parameter');
+		$$self{'_parameters'} = $params;
+		$$self{'_parameters_null'} = $params_null;
+		$n_params = $#$params + 1;
+	}
+	# and the results
+	if(exists $$self{'Results'})
+	{
+		my $results = [];
+		my $results_null = [];
+		parse_vars_list($$self{'Results'}, $results, $results_null);
+		$$self{'_results'} = $results;
+		$$self{'_results_null'} = $results_null;
+	}
+	# and check that the parameters in the SQL are in order, and the right number
+	my $last_insert_mark = 0;
+	my $sql = $$self{'Statement'};
+	while($sql =~ m/\$(\d+)/g)
+	{
+		if($1 != ($last_insert_mark + 1))
+		{
+			die "Insert marks are not in order and/or have missing numbers in $sql"
+		}
+		$last_insert_mark++;
+	}
+	die "Number of parmaters provided does not match the number of insert marks in $sql"
+		unless $last_insert_mark == $n_params;
+
+	bless $self;
+	$self 
+}
+
+sub get_name
+{
+	my ($self) = @_;
+	$$self{'Name'}
+}
+
+# get the results, as CppVariables
+sub get_results
+{
+	my ($self) = @_;
+	if(exists $$self{'_results'})
+	{
+		return (@{$$self{'_results'}})
+	}
+	# no results
+	return ();
+}
+
+# is it based on the generic query ('runtime' as statement)
+sub dervied_from_DatabaseQueryGeneric
+{
+	my ($self) = @_;
+	$self->is_runtime_statement();
+}
+
+# -----------------------------------------------------------
+# interface for WebAppFramework::ArgumentAdaptor
+sub get_arguments
+{
+	my ($self) = @_;
+	if(exists $$self{'_parameters'})
+	{
+		# return parameters
+		return (@{$$self{'_parameters'}});
+	}
+	# return nothing, none set
+	return ();
+}
+sub get_arguments_null
+{
+	my ($self) = @_;
+	if(exists $$self{'_parameters_null'})
+	{
+		# return parameters
+		return (@{$$self{'_parameters_null'}});
+	}
+	# return nothing, none set
+	return ();
+}
+sub some_arguments_may_be_null
+{
+	# some may be null
+	return 1;
+}
+# -----------------------------------------------------------
+
+# is a runtime statement?
+sub is_runtime_statement
+{
+	my ($self) = @_;
+	return ($$self{'Statement'} eq 'runtime')
+}
+
+# got an auto increment value?
+sub has_autoincrement
+{
+	my ($self) = @_;
+	return exists $$self{'AutoIncrementValue'}
+}
+
+# null allowed strings are NULL? or blank entirely
+sub parse_null_allowed
+{
+	my $v = $_[0];
+	return 0 if $v eq '';
+	return 1 if $v eq 'NULL?';
+	die "Unknown 'null allowed' parameter '$v' passed to Database::Query, must be NULL? or blank."
+}
+
+sub parse_vars_list
+{
+	my ($list, $vars_out, $vars_null_out, $default_name) = @_;
+
+	# split up and look at each param in turn
+	my $num = 1;
+	for(split /,\s*/,$list)
+	{
+		# make a cpp variable from it
+		s/\A\s+//; s/\s+\Z//;
+		my ($type,$name,$null_allowed) = split /\s+/,$_;
+		if($type eq '')
+		{
+			die "Variable specified with no type to Database::Query, suprious final , maybe?"
+		}
+		# make default name if none specified
+		if($name eq '')
+		{
+			if($default_name ne '')
+			{
+				$name = $default_name.$num;
+			}
+			else
+			{
+				die "Must specify a name for all values in $list for Database::Query"
+			}
+		}
+		# check and establish null allowed
+		$null_allowed = parse_null_allowed($null_allowed);
+
+		# build a cpp variable from it and add info to arrays
+		push @$vars_out,cppvar($type,$name);
+		push @$vars_null_out,$null_allowed;
+		
+		$num++;
+	}
+}
+
+sub generate_h
+{
+	my ($self) = @_;
+	
+	my $class_name = $$self{'Name'};
+	# boilerplate class start, with execute function
+	my ($execute_args,undef) = $self->execute_args();
+	my $baseclass = ($self->is_runtime_statement())?'DatabaseQueryGeneric':'DatabaseQuery';
+	my $constructor_argextra = ($self->is_runtime_statement())?', const char *pStatement, bool VendoriseStatement = false':'';
+	my $h = <<__E;
+class $class_name : public $baseclass
+{
+public:
+	$class_name(DatabaseConnection &rConnection$constructor_argextra);
+	~$class_name();
+private:
+	// no copying
+	$class_name(const $class_name &);
+	$class_name &operator=(const $class_name &);
+public:
+__E
+	# execute functions only output if not a runtime statement
+	unless($self->is_runtime_statement())
+	{
+		$h .= <<__E;
+	// Execute function
+	void Execute($execute_args);
+__E
+	}
+
+	my ($need_do,$do_return_type,$do_value_source) = $self->do_function_needed();
+	if($need_do)
+	{
+		my $args = 'DatabaseConnection &rConnection' . (($execute_args ne '')?', ':'') . $execute_args;
+		$h .= "\t// static Do() one shot execute function\n";
+		$h .= "\tstatic $do_return_type Do($args);\n";
+	}
+
+	# auto increment id function
+	if(exists $$self{'AutoIncrementValue'})
+	{
+		$h .= "\t// Retrieve the inserted ID\n";
+		$h .= "\tint32_t InsertedValue() {return mInsertedValue;}\n";
+	}
+
+
+	# then generate the results functions
+	if(exists $$self{'_results'})
+	{
+		$h .= "\t// Field retrieval functions\n";
+		my $col = 0;
+		for my $r (@{$$self{'_results'}})
+		{
+			my $nm = $r->name();
+			if($r->is_int_type())
+			{
+				$h .= <<__E;
+	int32_t Get$nm() {return GetFieldInt($col);}
+	void Get$nm(int32_t &rFieldOut) {GetFieldInt($col, rFieldOut);}
+__E
+			}
+			elsif($r->is_string_type())
+			{
+				$h .= <<__E;
+	std::string Get$nm() {return GetFieldString($col);}
+	void Get$nm(std::string &rFieldOut) {GetFieldString($col, rFieldOut);}
+__E
+			}
+			else
+			{
+				die "result ".$r->name()." doesn't appear to have string or integer type in Database::Query"
+			}
+			if(${$$self{'_results_null'}}[$col])
+			{
+				# generate an IsNull() column for this
+				$h .= "\tbool Is${nm}Null() {return IsFieldNull($col);}\n";
+			}
+			
+			$col++;
+		}
+	}
+
+	# and then finish off the class
+	unless($self->is_runtime_statement())
+	{
+		$h .= <<__E;
+protected:
+	virtual const char *GetSQLStatement();
+	virtual bool StatementNeedsVendorisation();
+__E
+	}
+
+	if(exists $$self{'AutoIncrementValue'})
+	{
+	$h .= <<__E;
+private:
+	int32_t mInsertedValue;
+__E
+	}
+
+	$h .= <<__E;
+};
+__E
+
+	# lovely!
+	$h
+}
+
+sub generate_cpp
+{
+	my ($self) = @_;
+	
+	my $class_name = $$self{'Name'};
+	# boilerplate class start, with execute function
+	my ($execute_args,$pass_on_args) = $self->execute_args();
+	my $sql = $$self{'Statement'};
+	$sql =~ tr/\t\n/  /;
+	$sql =~ s/\"/\\\"/g;
+	my $insertid_init = (exists $$self{'AutoIncrementValue'})?",\n\t  mInsertedValue(-1)":'';
+	my $needsvendorisation = ($sql =~ m/\`/)?'true':'false';
+	my $baseclass = 'DatabaseQuery';
+	my ($constructor_argextra,$constructor_base_extra);
+	if($self->is_runtime_statement())
+	{
+		$constructor_argextra = ', const char *pStatement, bool VendoriseStatement';
+		$constructor_base_extra = ', pStatement, VendoriseStatement';
+		$baseclass = 'DatabaseQueryGeneric';
+	}
+	my $cpp = <<__E;
+${class_name}::${class_name}(DatabaseConnection &rConnection$constructor_argextra)
+	: $baseclass(rConnection$constructor_base_extra)$insertid_init
+{
+}
+${class_name}::~${class_name}()
+{
+}
+__E
+	unless($self->is_runtime_statement())
+	{
+		$cpp .= <<__E;
+const char *${class_name}::GetSQLStatement()
+{
+	return "$sql";
+}
+bool ${class_name}::StatementNeedsVendorisation()
+{
+	return $needsvendorisation;
+}
+void ${class_name}::Execute($execute_args)
+{
+__E
+	
+		unless(exists $$self{'_parameters'})
+		{
+			# no parameters, just use basic execute function
+			$cpp .= "\tDatabaseQuery::Execute(0, 0, 0);\n";
+		}
+		else
+		{
+			# marshall parameters, then call base class		
+			my $params = $$self{'_parameters'};
+			my $params_null = $$self{'_parameters_null'};
+			my $n_params = $#$params + 1;
+			my $marshalled_types = '';
+			my $marshalled_params = '';
+			
+			for(my $p = 0; $p <= $#$params; $p++)
+			{
+				$marshalled_types .= ', ' unless $marshalled_types eq '';
+				$marshalled_params .= ', ' unless $marshalled_params eq '';
+				
+				my $nm = $$params[$p]->name();
+				if($$params[$p]->is_int_type())
+				{
+					$marshalled_types .= 'Database::Type_Int32';
+					$marshalled_params .= ($$params_null[$p])?$nm:'&'.$nm;
+				}
+				elsif($$params[$p]->is_string_type())
+				{
+					$marshalled_types .= 'Database::Type_String';
+					$marshalled_params .= ($$params_null[$p])?"(($nm == 0)?(0):($nm->c_str()))":$nm.'.c_str()';
+				}
+			}
+			
+			# write code
+			$cpp .= <<__E;
+	static const Database::FieldType_t parameterTypes[$n_params]
+			= {$marshalled_types};
+	const void *parameters[$n_params]
+			= {$marshalled_params};
+	DatabaseQuery::Execute($n_params, parameterTypes, parameters);
+__E
+		}
+	
+		# get insert value?
+		if(exists $$self{'AutoIncrementValue'})
+		{
+			my ($tablename,$columnname) = split /\s+/,$$self{'AutoIncrementValue'};
+			$cpp .= qq!\tmInsertedValue = GetConnection().GetLastAutoIncrementValue("$tablename", "$columnname");\n!;
+		}
+	
+		# finish function
+		$cpp .= <<__E;
+}
+__E
+	}
+
+	# write a Do function?
+	my ($need_do,$do_return_type,$do_value_source) = $self->do_function_needed();
+	if($need_do)
+	{
+		my $args = 'DatabaseConnection &rConnection' . (($execute_args ne '')?', ':'') . $execute_args;
+		$cpp .= <<__E;
+$do_return_type ${class_name}::Do($args)
+{
+	${class_name} query(rConnection);
+	query.Execute($pass_on_args);
+	return $do_value_source;
+}
+__E
+	}
+
+	#lovely
+	$cpp
+}
+
+# returns (boolean, return type, result source)
+sub do_function_needed
+{
+	my ($self) = @_;
+
+	if(exists $$self{'Flag_SingleValue'})
+	{
+		# check for single result being specified properly
+		if(!exists $$self{'_results'} || $#{$$self{'_results'}} != 0)
+		{
+			die "In query, when SingleValue flag is specified, one and only one result must be specified";
+		}
+		# work out values
+		my $single_res = ${$$self{'_results'}}[0];
+		my $return_type = ($single_res->is_int_type())?'int32_t':'std::string';
+		my $fntype = ($single_res->is_int_type())?'Int':'String';
+		return (1, $return_type, "query.GetSingleValue$fntype()")
+	}
+
+	if(exists $$self{'AutoIncrementValue'})
+	{
+		return (1, 'int32_t', 'query.mInsertedValue')
+	}
+
+	# no do function should be written
+	(0, undef)
+}
+
+sub execute_args
+{
+	my ($self) = @_;
+
+	return '' unless exists $$self{'_parameters'};
+
+	my $params = $$self{'_parameters'};
+	my $params_null = $$self{'_parameters_null'};
+	my $a = '';
+	my $b = '';
+	for(my $p = 0; $p <= $#$params; $p++)
+	{
+		$a .= ', ' if $a ne '';
+		$b .= ', ' if $b ne '';
+		if($$params[$p]->is_int_type())
+		{
+			$a .= ($$params_null[$p])?'int32_t *':'int32_t ';
+		}
+		elsif($$params[$p]->is_string_type())
+		{
+			$a .= ($$params_null[$p])?'const std::string *':'const std::string ';
+		}
+		else
+		{
+			die "parameter ".$$params[$p]->name()." doesn't appear to have string or integer type in Database::Query"
+		}
+		$a .= $$params[$p]->name();
+		$b .= $$params[$p]->name();
+	}
+	($a,$b)
+}
+
+1;
+

Added: box/features/codeforintegration/lib/database/DatabaseConnection.cpp
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseConnection.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseConnection.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,194 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseConnection.cpp
+//		Purpose: Database connection abstraction
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "DatabaseConnection.h"
+#include "autogen_DatabaseException.h"
+#include "DatabaseDriver.h"
+#include "DatabaseDriverRegistration.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::DatabaseConnection()
+//		Purpose: Constructor
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseConnection::DatabaseConnection()
+	: mpDriver(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::~DatabaseConnection()
+//		Purpose: Destructor
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseConnection::~DatabaseConnection()
+{
+	// Disconnect if a connection is active
+	if(mpDriver != 0)
+	{
+		Disconnect();
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::Connect(const std::string &, const std::string &, int)
+//		Purpose: Connect to a database, given a driver name and a driver, and a timeout value (in ms)
+//				 connection string.
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseConnection::Connect(const std::string &rDriverName, const std::string &rConnectionString, int Timeout)
+{
+	if(mpDriver != 0)
+	{
+		THROW_EXCEPTION(DatabaseException, AlreadyConnected)
+	}
+
+	// Create a driver object
+	DatabaseDriver *pdriver = Database::CreateInstanceOfRegisteredDriver(rDriverName.c_str());
+
+	try
+	{
+		// Connect
+		pdriver->Connect(rConnectionString, Timeout);
+	}
+	catch(...)
+	{
+		delete pdriver;
+		pdriver = 0;
+		throw;
+	}
+
+	// Store connection
+	mpDriver = pdriver;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::Disconnect()
+//		Purpose: Disconnect from the database server
+//		Created: 8/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseConnection::Disconnect()
+{
+	if(mpDriver == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, NotConnected)
+	}
+	
+	// Ask driver to disconnect
+	mpDriver->Disconnect();
+	
+	// Clean up
+	delete mpDriver;
+	mpDriver = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::GetDriver()
+//		Purpose: Return a reference to the driver for the connection -- internal
+//				 use only.
+//		Created: 8/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDriver &DatabaseConnection::GetDriver() const
+{
+	if(mpDriver == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, NotConnected)
+	}
+
+	return *mpDriver;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::GetLastAutoIncrementValue(const char *, const char *)
+//		Purpose: Return the value of the auto_increment column in the last INSERT
+//				 statement (use generic type `AUTO_INCREMENT_INT for column type).
+//				 Specify the table and column names, as some drivers require this info.
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+int32_t DatabaseConnection::GetLastAutoIncrementValue(const char *TableName, const char *ColumnName)
+{
+	if(mpDriver == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, NotConnected)
+	}
+
+	// Pass on to driver	
+	return mpDriver->GetLastAutoIncrementValue(TableName, ColumnName);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::QuoteString(const char *, std::string &)
+//		Purpose: Quote a string in a way which is acceptable to the database.
+//				 Can only be used after the database is connected.
+//		Created: 11/2/05
+//
+// --------------------------------------------------------------------------
+void DatabaseConnection::QuoteString(const char *pString, std::string &rStringQuotedOut) const
+{
+	if(mpDriver == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, NotConnected)
+	}
+	
+	// Pass on to driver	
+	mpDriver->QuoteString(pString, rStringQuotedOut);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseConnection::GetDriverName()
+//		Purpose: Return the name of the driver for the current connection
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+const char *DatabaseConnection::GetDriverName() const
+{
+	if(mpDriver == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, NotConnected)
+	}
+
+	// Pass on to driver	
+	return mpDriver->GetDriverName();
+}
+
+


Property changes on: box/features/codeforintegration/lib/database/DatabaseConnection.cpp
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/DatabaseConnection.h
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseConnection.h	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseConnection.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,55 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseConnection.h
+//		Purpose: Database connection
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASECONNECTION__H
+#define DATABASECONNECTION__H
+
+#include <string>
+
+class DatabaseDriver;
+class DatabaseQuery;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DatabaseConnection
+//		Purpose: Database connection abstraction
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+class DatabaseConnection
+{
+	friend class DatabaseQuery;
+public:
+	DatabaseConnection();
+	~DatabaseConnection();
+private:
+	// No copying
+	DatabaseConnection(const DatabaseConnection &);
+	DatabaseConnection &operator=(const DatabaseConnection&);
+
+public:
+	void Connect(const std::string &rDriverName, const std::string &rConnectionString, int Timeout);
+	void Disconnect();
+
+	int32_t GetLastAutoIncrementValue(const char *TableName, const char *ColumnName);
+
+	const char *GetDriverName() const;
+
+	// Expose the string quoting function, so that callers can use it to dynamically build queries
+	void QuoteString(const char *pString, std::string &rStringQuotedOut) const;
+
+protected:
+	DatabaseDriver &GetDriver() const;
+
+private:
+	DatabaseDriver *mpDriver;
+};
+
+#endif // DATABASECONNECTION__H


Property changes on: box/features/codeforintegration/lib/database/DatabaseConnection.h
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/DatabaseDriverRegistration.cpp
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseDriverRegistration.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseDriverRegistration.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,143 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseDriverRegistration.cpp
+//		Purpose: Create a new driver -- contains a list of all drivers
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+
+#include "DatabaseDriverRegistration.h"
+#include "autogen_DatabaseException.h"
+
+// Find out which driver modules are available
+#include "../../local/modules.h"
+
+// load module headers
+#ifdef MODULE_lib_dbdrv_sqlite
+	#include "DbDriverSqlite.h"
+#endif
+#ifdef MODULE_lib_dbdrv_mysql
+	#include "DbDriverMySQL.h"
+#endif
+#ifdef MODULE_lib_dbdrv_postgresql
+	#include "DbDriverPostgreSQL.h"
+#endif
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    Database::CreateInstanceOfRegisteredDriver(const char *)
+//		Purpose: Returns an initialised (but unconnected) driver object for a
+//				 given driver name. Will exception if the driver is unavailable.
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDriver *Database::CreateInstanceOfRegisteredDriver(const char *DriverName)
+{
+#ifdef MODULE_lib_dbdrv_sqlite
+	if(::strcmp(DriverName, "sqlite") == 0)
+	{
+		return new DbDriverSqlite;
+	}
+#endif
+#ifdef MODULE_lib_dbdrv_mysql
+	if(::strcmp(DriverName, "mysql") == 0)
+	{
+		return new DbDriverMySQL;
+	}
+#endif
+#ifdef MODULE_lib_dbdrv_postgresql
+	if(::strcmp(DriverName, "postgresql") == 0)
+	{
+		return new DbDriverPostgreSQL;
+	}
+#endif
+
+	THROW_EXCEPTION(DatabaseException, DriverNotAvailable)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    Database::DriverAvailable(const char *)
+//		Purpose: Returns true if a particular driver is available
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+bool Database::DriverAvailable(const char *DriverName)
+{
+#ifdef MODULE_lib_dbdrv_sqlite
+	if(::strcmp(DriverName, "sqlite") == 0)
+	{
+		return true;
+	}
+#endif
+#ifdef MODULE_lib_dbdrv_mysql
+	if(::strcmp(DriverName, "mysql") == 0)
+	{
+		return true;
+	}
+#endif
+#ifdef MODULE_lib_dbdrv_postgresql
+	if(::strcmp(DriverName, "postgresql") == 0)
+	{
+		return true;
+	}
+#endif
+
+	return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    Database::DriverList(std::string &)
+//		Purpose: Returns the number of drivers available, and fills in a string
+//				 with a list of those drivers.
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+int Database::DriverList(const char **pDriverList)
+{
+	if(pDriverList)
+	{
+		*pDriverList =
+#ifdef MODULE_lib_dbdrv_sqlite
+		 "sqlite "\
+
+#endif
+#ifdef MODULE_lib_dbdrv_mysql
+		 "mysql "\
+
+#endif
+#ifdef MODULE_lib_dbdrv_postgresql
+		 "postgresql "\
+
+#endif
+			"";
+	}
+
+	int nDrivers = 0;
+#ifdef MODULE_lib_dbdrv_sqlite
+	++nDrivers;
+#endif
+#ifdef MODULE_lib_dbdrv_mysql
+	++nDrivers;
+#endif
+#ifdef MODULE_lib_dbdrv_postgresql
+	++nDrivers;
+#endif
+
+	return nDrivers;
+}
+


Property changes on: box/features/codeforintegration/lib/database/DatabaseDriverRegistration.cpp
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/DatabaseDriverRegistration.h
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseDriverRegistration.h	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseDriverRegistration.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,23 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseDriverRegistration.h
+//		Purpose: Database driver registration
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASEDRIVERREGISTRATION__H
+#define DATABASEDRIVERREGISTRATION__H
+
+class DatabaseDriver;
+
+namespace Database
+{
+	DatabaseDriver *CreateInstanceOfRegisteredDriver(const char *DriverName);
+	bool DriverAvailable(const char *DriverName);
+	int DriverList(const char **pDriverList = 0);
+};
+
+#endif // DATABASEDRIVERREGISTRATION__H
+

Added: box/features/codeforintegration/lib/database/DatabaseQuery.cpp
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseQuery.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseQuery.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,374 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseQuery.cpp
+//		Purpose: Database query abstraction
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "DatabaseQuery.h"
+#include "DatabaseConnection.h"
+#include "autogen_DatabaseException.h"
+#include "DatabaseDriver.h"
+
+#include "MemLeakFindOn.h"
+
+#define MAX_GENERIC_ARGUMENTS		10
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::DatabaseQuery()
+//		Purpose: Constructor
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseQuery::DatabaseQuery(DatabaseConnection &rConnection)
+	: mConnection(rConnection),
+	  mpQuery(spNullQuery)
+{
+	ASSERT(mpQuery != 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::~DatabaseQuery()
+//		Purpose: Destructor
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseQuery::~DatabaseQuery()
+{
+	if(mpQuery != spNullQuery)
+	{
+		Finish();
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::Execute(int, const FieldType_t *, void **)
+//		Purpose: Execute a query
+//		Created: 7/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQuery::Execute(int NumberParameters, const Database::FieldType_t *pParameterTypes, const void **pParameters)
+{
+	// Check parameters
+	if(NumberParameters < 0 || (NumberParameters > 0 && (pParameterTypes == 0 || pParameters == 0)))
+	{
+		THROW_EXCEPTION(DatabaseException, BadQueryParameters)
+	}
+	if(NumberParameters > Database::MaxParameters)
+	{
+		THROW_EXCEPTION(DatabaseException, TooManyParameters)
+	}
+
+	// Need a query object?
+	if(mpQuery == spNullQuery)
+	{
+		// No object currently held -- create a new object from the driver
+		DatabaseDriver &rdriver(mConnection.GetDriver());
+		mpQuery = rdriver.Query();
+		// Paranoid check
+		if(mpQuery == NULL)
+		{
+			mpQuery = spNullQuery;
+		}
+	}
+
+	// Execute the query on the driver's query object
+	if(StatementNeedsVendorisation())
+	{
+		// Statement needs running through the vendorisation routine
+		DatabaseDriver &rdriver(mConnection.GetDriver());
+		std::string statement;
+		VendoriseStatement(rdriver, GetSQLStatement(), statement);
+		mpQuery->Execute(statement.c_str(), NumberParameters, pParameterTypes, pParameters);
+	}
+	else
+	{
+		// Plain statement can be used
+		mpQuery->Execute(GetSQLStatement(), NumberParameters, pParameterTypes, pParameters);
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::VendoriseStatement(const char *, std::string &)
+//		Purpose: Replace generic representations in an SQL string with vendor
+//				 specific SQL.
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQuery::VendoriseStatement(DatabaseDriver &rDriver, const char *Input, std::string &rOutput)
+{
+	ASSERT(Input != NULL);
+	rOutput = std::string(); // ie clear();
+	
+	// Scan for ` characters
+	int b = 0;
+	int p = 0;
+	while(Input[p] != '\0')
+	{
+		if(Input[p] == '`')
+		{
+			// Start of generic representation
+			// Dump previous string to output
+			if(b != p)
+			{
+				rOutput.append(Input + b, p - b);
+			}
+
+			// Details of element
+			const char *element[MAX_GENERIC_ARGUMENTS];
+			int elementLength[MAX_GENERIC_ARGUMENTS];
+			int numberElements = 1;
+			
+			// First element, which is the name of the string
+			++p;
+			b = p;
+			element[0] = Input + p;
+			while(Input[p] != '\0' && ((Input[p] >= 'A' && Input[p] <= 'Z') || Input[p] == '_'))
+			{
+				++p;
+			}
+			elementLength[0] = p - b;
+			
+			// Any arguments?
+			if(Input[p] == '(')
+			{
+				// Yes!
+				++p;
+				while(Input[p] != ')')
+				{
+					if(numberElements >= MAX_GENERIC_ARGUMENTS)
+					{
+						THROW_EXCEPTION(DatabaseException, TooManyArgumentsToGeneric)
+					}
+					element[numberElements] = Input + p;
+					int eb = p;
+					while(Input[p] != ',' && Input[p] != ')')
+					{
+						++p;
+					}
+					elementLength[numberElements] = p - eb;
+					++numberElements;
+					if(Input[p] != ')')
+					{
+						// Move on
+						++p;
+					}
+				}
+				// Make sure final zero length args work OK
+				if(Input[p] == ')' && Input[p-1] == ',')
+				{
+					if(numberElements >= MAX_GENERIC_ARGUMENTS)
+					{
+						THROW_EXCEPTION(DatabaseException, TooManyArgumentsToGeneric)
+					}
+					element[numberElements] = Input + p;
+					elementLength[numberElements] = 0;
+					++numberElements;
+				}
+				// Move on to the next character, skipping the final ')'
+				++p;
+			}
+			
+			// Translate it and add on to the output
+			std::string vendor;
+			rDriver.TranslateGeneric(numberElements, element, elementLength, vendor);
+			rOutput += vendor;
+			
+			// Ready for next untranslated section
+			b = p;
+		}
+		else
+		{
+			// Examine next character
+			++p;
+		}
+	}
+	// Add the end of the string
+	if(b != p)
+	{
+		rOutput.append(Input + b, p - b);
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::Finish()
+//		Purpose: Explicitly deallocate the query resources. Can leave it up to the
+//				 destructor in most circumstances.
+//		Created: 8/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQuery::Finish()
+{
+	// Check that a query is in progress
+	if(mpQuery == spNullQuery)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Tell query to finish
+	mpQuery->Finish();
+
+	// Clean up resource
+	delete mpQuery;
+	mpQuery = spNullQuery;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::GetSingleValueInt()
+//		Purpose: Return a single value
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+int32_t DatabaseQuery::GetSingleValueInt()
+{
+	CheckSingleValueConditions();
+	return mpQuery->GetFieldInt(0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::GetSingleValueString(std::string &)
+//		Purpose: Return a single value
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQuery::GetSingleValueString(std::string &rStringOut)
+{
+	CheckSingleValueConditions();
+	mpQuery->GetFieldString(0, rStringOut);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::CheckSingleValueConditions()
+//		Purpose: Protected. Check the conditions are right to retrieve a single value.
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQuery::CheckSingleValueConditions()
+{
+	if(mpQuery == spNullQuery)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mpQuery->GetNumberColumns() != 1 || mpQuery->GetNumberRows() != 1)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryContainedMoreThanSingleValue)
+	}
+	// Need to get the query ready?
+	if(!HaveRow())
+	{
+		// No data ready yet, get that first row
+		Next();
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQuery::PrepareForMultipleExecutions()
+//		Purpose: Should this queries be prepared for more efficient multiple
+//				 executions?
+//		Created: 8/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseQuery::MultipleExecutions_t DatabaseQuery::PrepareForMultipleExecutions()
+{
+	return NoPrepare;
+}
+
+
+
+
+// ===============================================================================================================
+
+// Hide from the outside world
+namespace
+{
+
+// A null query class which will just exception on any call
+class NullQuery : public DatabaseDrvQuery
+{
+public:
+	NullQuery()
+		: DatabaseDrvQuery()
+	{
+	}
+	~NullQuery()
+	{
+	}
+	void Execute(const char *SQLStatement, int NumberParameters, const Database::FieldType_t *pParameterTypes, const void **pParameters)
+	{
+		THROW_EXCEPTION(DatabaseException, Internal)
+	}
+	int GetNumberChanges() const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	int GetNumberRows() const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	int GetNumberColumns() const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	bool Next()
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	bool HaveRow() const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	void Finish()
+	{
+		// Should never be called -- check before Finish() is called in driver query
+		THROW_EXCEPTION(DatabaseException, Internal)
+	}
+	bool IsFieldNull(int Column) const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	int32_t GetFieldInt(int Column) const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	void GetFieldString(int Column, std::string &rStringOut) const
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+};
+
+// Set up null query static member var
+NullQuery nullQuery;
+DatabaseDrvQuery *DatabaseQuery::spNullQuery = &nullQuery;
+
+} // end hiding namespace
+


Property changes on: box/features/codeforintegration/lib/database/DatabaseQuery.cpp
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/DatabaseQuery.h
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseQuery.h	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseQuery.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,187 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseQuery.h
+//		Purpose: Database query abstraction
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASEQUERY__H
+#define DATABASEQUERY__H
+
+#include <string>
+
+#include "DatabaseDriver.h"
+
+class DatabaseConnection;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DatabaseQuery
+//		Purpose: Database query abstraction
+//		Created: 1/5/04
+//
+// --------------------------------------------------------------------------
+class DatabaseQuery
+{
+public:
+	DatabaseQuery(DatabaseConnection &rConnection);
+	virtual ~DatabaseQuery();
+private:
+	// No copying
+	DatabaseQuery(const DatabaseQuery &);
+	DatabaseQuery &operator=(const DatabaseQuery &);
+public:
+
+	// The derived class will provide some nicer methods to call
+	void Execute(int NumberParameters, const Database::FieldType_t *pParameterTypes, const void **pParameters);
+
+	void Finish();
+
+	// Inline implementation of various functions which call an internal "driver query".
+	// It's safe to just call the driver query via the pointer, as there will either be a valid pointer,
+	// or a pointer to the special null query class.
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::GetNumberChanges()
+	//		Purpose: Return the number of rows which were changed by the last query.
+	//		Created: 10/5/04
+	//
+	// --------------------------------------------------------------------------
+	int GetNumberChanges() const {return mpQuery->GetNumberChanges();}
+
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::GetNumberRows()
+	//		Purpose: Return the number of rows in the result set
+	//		Created: 10/5/04
+	//
+	// --------------------------------------------------------------------------
+	int GetNumberRows() const {return mpQuery->GetNumberRows();}
+
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::GetNumberColumns()
+	//		Purpose: Return the number of columns in the result set
+	//		Created: 10/5/04
+	//
+	// --------------------------------------------------------------------------
+	int GetNumberColumns() const {return mpQuery->GetNumberColumns();}
+
+	
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::Next()
+	//		Purpose: Move the query to the next row, returning true if there is
+	//				 a row to retrieve after moving forward. (ie HaveRow() will
+	//				 return true as well)
+	//		Created: 7/5/04
+	//
+	// --------------------------------------------------------------------------
+	bool Next() {return mpQuery->Next();}
+
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::HaveRow()
+	//		Purpose: Is there a row of data available
+	//		Created: 7/5/04
+	//
+	// --------------------------------------------------------------------------
+	bool HaveRow() const {return mpQuery->HaveRow();}
+
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::IsFieldNull(int)
+	//		Purpose: Return true if the nth field is null
+	//		Created: 8/5/04
+	//
+	// --------------------------------------------------------------------------
+	bool IsFieldNull(int Column) const {return mpQuery->IsFieldNull(Column);}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::GetField[Int|String](int[, reference])
+	//		Purpose: Get the nth field as the named type
+	//		Created: 7/5/04
+	//
+	// --------------------------------------------------------------------------
+	inline int32_t GetFieldInt(int Column) const {return mpQuery->GetFieldInt(Column);}
+	inline void GetFieldInt(int Column, int32_t &rIntOut) const {rIntOut = mpQuery->GetFieldInt(Column);}
+	inline std::string GetFieldString(int Column) const
+	{
+		std::string str;
+		mpQuery->GetFieldString(Column, str);
+		return str;
+	}
+	inline void GetFieldString(int Column, std::string &rStringOut) const {mpQuery->GetFieldString(Column, rStringOut);}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    DatabaseQuery::GetSingleValue[Int|String]([reference])
+	//		Purpose: When a query returns a single row containing a single column,
+	//				 return that value. Will exception if row and int count aren't equal to 1.
+	//				 Not const, as may call Next() to retrieve the first row.
+	//		Created: 10/5/04
+	//
+	// --------------------------------------------------------------------------
+	int32_t GetSingleValueInt();
+	inline void GetSingleValueInt(int32_t &rIntOut) {rIntOut = GetSingleValueInt();}
+	inline std::string GetSingleValueString()
+	{
+		std::string str;
+		GetSingleValueString(str);
+		return str;
+	}
+	void GetSingleValueString(std::string &rStringOut);
+
+	// For testing
+	static void TEST_VendoriseStatement(DatabaseDriver &rDriver, const char *Input, std::string &rOutput)
+		{ VendoriseStatement(rDriver, Input, rOutput); }
+
+protected:
+	// Must be implemented by derived classes
+	// Get the base SQL statement (not a std::string as this will mostly be a constant)
+	virtual const char *GetSQLStatement() = 0;
+	// Does the string need to be modified for the vendor?
+	virtual bool StatementNeedsVendorisation() = 0;
+	// Should the statement be prepared on the database server? (for repeated executions)
+	typedef enum
+	{
+		NoPrepare = 0,			// one shot execution
+		PrepareInstance = 1,	// prepared statement held for entire lifetime of object
+		PrepareGlobal = 2		// prepared statement held for lifetime of connection
+	} MultipleExecutions_t;
+	virtual MultipleExecutions_t PrepareForMultipleExecutions();
+
+	void CheckSingleValueConditions();
+	
+	DatabaseConnection &GetConnection() {return mConnection;}
+
+private:
+	static void VendoriseStatement(DatabaseDriver &rDriver, const char *Input, std::string &rOutput);
+
+private:
+	DatabaseConnection &mConnection;
+	DatabaseDrvQuery *mpQuery;
+
+	// Pointer to null query class
+	static DatabaseDrvQuery *spNullQuery;
+};
+
+
+#endif // DATABASEQUERY__H


Property changes on: box/features/codeforintegration/lib/database/DatabaseQuery.h
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/DatabaseQueryGeneric.cpp
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseQueryGeneric.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseQueryGeneric.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,234 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseQueryGeneric.cpp
+//		Purpose: Generic database query
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdarg.h>
+
+#include "DatabaseQueryGeneric.h"
+#include "autogen_DatabaseException.h"
+
+#include "MemLeakFindOn.h"
+
+// Maximum number of parameters to the variable arguments Execute() function
+#define MAX_PARAMETERS		32
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::DatabaseQueryGeneric(DatabaseConnection &, const char *)
+//		Purpose: Constructor, taking query and SQL statement
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseQueryGeneric::DatabaseQueryGeneric(DatabaseConnection &rConnection, const char *pStatement, bool VendoriseStatement)
+	: DatabaseQuery(rConnection),
+	  mpStatement(pStatement),
+	  mVendoriseStatement(VendoriseStatement)
+{
+	ASSERT(mpStatement != 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::~DatabaseQueryGeneric()
+//		Purpose: Destructor
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseQueryGeneric::~DatabaseQueryGeneric()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::Execute()
+//		Purpose: Execute statement, with no parameters
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQueryGeneric::Execute()
+{
+	// Marshall no parameters
+	DatabaseQuery::Execute(0, NULL, NULL);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::Execute(int)
+//		Purpose: Execute a statement, with one integer value
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQueryGeneric::Execute(int Value)
+{
+	static const Database::FieldType_t paramTypes[] = {Database::Type_Int32};
+	const void *params[] = {&Value};
+	DatabaseQuery::Execute(1, paramTypes, params);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::Execute(const std::string &)
+//		Purpose: Execute a statement, with one string value
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQueryGeneric::Execute(const std::string &rValue)
+{
+	static const Database::FieldType_t paramTypes[] = {Database::Type_String};
+	const void *params[] = {rValue.c_str()};
+	DatabaseQuery::Execute(1, paramTypes, params);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::Execute(const char *)
+//		Purpose: Execute a statement, with one string value (can be NULL)
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQueryGeneric::Execute(const char *pValue)
+{
+	static const Database::FieldType_t paramTypes[] = {Database::Type_String};
+	const void *params[] = {pValue};
+	DatabaseQuery::Execute(1, paramTypes, params);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::Execute(const char *, ...)
+//		Purpose: Execute a statement with multiple, typed, values.
+//				 Within types, each char represents one argument. i = integer (value, cannot be NULL),
+//				 I = integer (pointer, can be null), s = const char * string (pointer, can be NULL),
+//				 S = std::string (pointer, can be null),
+//				 N = null (corresponding argument must be NULL or 0)
+//				 Not intended to be the most efficient way of doing things -- just convenient.
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseQueryGeneric::Execute(const char *Types, ...)
+{
+	// Quickly use no parameters version?
+	if(Types == NULL || Types[0] == '\0')
+	{
+		DatabaseQuery::Execute(0, NULL, NULL);
+		return;
+	}
+
+	// Build list of parameters
+	Database::FieldType_t paramTypes[MAX_PARAMETERS];
+	const void *params[MAX_PARAMETERS];
+	int32_t integers[MAX_PARAMETERS];
+	int numParams = 0;
+	va_list args;
+	va_start(args, Types);
+
+	const char *c = Types;
+	while(*c != '\0')
+	{
+		// Too many?
+		if(numParams >= MAX_PARAMETERS)
+		{
+			THROW_EXCEPTION(DatabaseException, TooManyParametersToExecute)
+		}
+		// Set the right type
+		switch(*c)
+		{
+		case 'i':
+			{
+				paramTypes[numParams] = Database::Type_Int32;
+				integers[numParams] = va_arg(args, int);
+				params[numParams] = &(integers[numParams]);
+				break;
+			}
+		case 'I':
+			{
+				paramTypes[numParams] = Database::Type_Int32;
+				params[numParams] = va_arg(args, int32_t*);
+				break;
+			}
+		case 's':
+			{
+				paramTypes[numParams] = Database::Type_String;
+				params[numParams] = va_arg(args, const char *);
+				break;
+			}
+		case 'S':
+			{
+				paramTypes[numParams] = Database::Type_String;
+				std::string *pstring = va_arg(args, std::string *);
+				params[numParams] = (pstring == NULL)?NULL:(pstring->c_str());
+				break;
+			}
+		case 'N':
+			{
+				paramTypes[numParams] = Database::Type_String;
+				params[numParams] = NULL;
+				const char *dummy = va_arg(args, const char *);
+				ASSERT(dummy == NULL);
+				break;
+			}
+		default:
+			{
+				THROW_EXCEPTION(DatabaseException, UnknownTypeCharacter)
+			}
+		}
+		// Next entry
+		++c;
+		++numParams;
+	}
+	va_end(args);
+
+	// Execute it
+	DatabaseQuery::Execute(numParams, paramTypes, params);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::GetSQLStatement()
+//		Purpose: Interface for implementation
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+const char *DatabaseQueryGeneric::GetSQLStatement()
+{
+	return mpStatement;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseQueryGeneric::StatementNeedsVendorisation()
+//		Purpose: Interface for implementation
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+bool DatabaseQueryGeneric::StatementNeedsVendorisation()
+{
+	return mVendoriseStatement;
+}
+
+

Added: box/features/codeforintegration/lib/database/DatabaseQueryGeneric.h
===================================================================
--- box/features/codeforintegration/lib/database/DatabaseQueryGeneric.h	                        (rev 0)
+++ box/features/codeforintegration/lib/database/DatabaseQueryGeneric.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,52 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseQueryGeneric.h
+//		Purpose: Generic database query
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASEQUERYGENERIC__H
+#define DATABASEQUERYGENERIC__H
+
+#include "DatabaseQuery.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DatabaseQueryGeneric
+//		Purpose: 
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+class DatabaseQueryGeneric : public DatabaseQuery
+{
+public:
+	DatabaseQueryGeneric(DatabaseConnection &rConnection, const char *pStatement, bool VendoriseStatement = false);
+	~DatabaseQueryGeneric();
+private:
+	// no copying
+	DatabaseQueryGeneric(const DatabaseQueryGeneric &);
+	DatabaseQueryGeneric &operator=(const DatabaseQueryGeneric &);
+public:
+
+	// Execution of the query
+	void Execute();
+	void Execute(int Value);
+	void Execute(const std::string &rValue);
+	void Execute(const char *pValue);
+	void Execute(const char *Types, ...);
+
+protected:
+	// Implementation details
+	virtual const char *GetSQLStatement();
+	virtual bool StatementNeedsVendorisation();
+
+private:
+	const char *mpStatement;
+	bool mVendoriseStatement;
+};
+
+#endif // DATABASEQUERYGENERIC__H
+

Added: box/features/codeforintegration/lib/database/makedbcreate.pl
===================================================================
--- box/features/codeforintegration/lib/database/makedbcreate.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/database/makedbcreate.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,172 @@
+#!/usr/bin/perl
+
+# Usage: makedbcreate.pl inputfile output.cpp output.h
+
+use strict;
+
+# Older versions of postgres require sequences to be dropped explicitly.
+# Set this to 1 to write code to drop these sequences for the postgresql driver.
+my $old_versions_of_postgres_require_sequence_drop = 0;
+
+
+
+my ($input_file, $output_cpp, $output_h) = @ARGV;
+if(!-f $input_file)
+{
+	die "Input file $input_file not found";
+}
+if($output_cpp eq '' || $output_h eq '')
+{
+	die "Output files not specified";
+}
+
+
+# read in input
+open IN,$input_file or die "Can't open $input_file";
+my $input;
+read IN,$input,-s $input_file;
+close IN;
+
+# function base name
+$input_file =~ m~([^/]+)\.[^\.]+\Z~;
+my $fn_base_name = $1;
+
+# open output files
+open CPP,'>'.$output_cpp or die "Can't open $output_cpp for writing";
+
+# write CPP boilerplate
+$output_h =~ m~([^/]+)\Z~;
+my $h_include = $1;
+print CPP <<__E;
+// automatically generated file, do not edit
+
+#include "Box.h"
+
+#include <string.h>
+
+#include "$h_include"
+#include "DatabaseConnection.h"
+#include "DatabaseQueryGeneric.h"
+#include "MemLeakFindOn.h"
+
+void ${fn_base_name}_Create(DatabaseConnection &rConnection)
+{
+__E
+
+# neaten up the input file, and split into queries
+$input =~ s/(#|--).*$//mg;
+my @to_drop;
+my @postgresql_seq_to_drop;
+for my $query (split /;/,$input)
+{
+	$query =~ s/\A\s+//;
+	$query =~ s/\s+\Z//;
+	next unless $query =~ m/\S/;
+	
+	if($query =~ m/CREATE\s+TABLE\s+(\w+)/si)
+	{
+		# make sure the table is dropped
+		my $table_name = $1;
+		push @to_drop,$table_name;
+		# PostgreSQL needs sequences for serial numbers dropped too
+		if($query =~ m/(\w+)\s+`AUTO_INCREMENT_INT/)
+		{
+			# need to drop this sequence too
+			push @postgresql_seq_to_drop,$table_name.'_'.$1.'_seq';
+		}
+	}
+	
+	my $vendorise = ($query =~ m/`/)?'true':'false';
+	
+	# write a create statement for this
+	my $sql;
+	for my $line (split /[\r\n]+/,$query)
+	{
+		$line =~ s/\A\s+//;
+		$line =~ s/\s+\Z//;
+		next unless $line =~ m/\S/;
+		$line =~ s/"/\\"/g;
+		$sql .= " \"\\\n\t\t\t\"" unless $sql eq '';
+		$sql .= $line;
+	}
+
+	print CPP <<__E;
+	{
+		DatabaseQueryGeneric create(rConnection, "$sql", $vendorise /* vendorise? */);
+		create.Execute();
+	}
+__E
+}
+
+# and then drop
+print CPP <<__E;
+}
+
+void ${fn_base_name}_Drop(DatabaseConnection &rConnection)
+{
+__E
+
+for(@to_drop)
+{
+	print CPP <<__E;
+	{
+		DatabaseQueryGeneric drop(rConnection, "DROP TABLE $_");
+		drop.Execute();
+	}
+__E
+}
+if($old_versions_of_postgres_require_sequence_drop)
+{
+	if($#postgresql_seq_to_drop >= 0)
+	{
+		# check for postgresql driver
+		print CPP <<__E;
+	if(::strcmp(rConnection.GetDriverName(), "postgresql") == 0)
+	{
+__E
+	
+		for(@postgresql_seq_to_drop)
+		{
+			print CPP <<__E;
+		{
+			DatabaseQueryGeneric drop(rConnection, "DROP SEQUENCE $_");
+			drop.Execute();
+		}
+__E
+		}
+	
+		print CPP <<__E;
+	}
+__E
+	}
+}
+
+print CPP <<__E;
+}
+
+
+__E
+
+close CPP;
+
+# write H file
+open H,'>'.$output_h or die "Can't open $output_h for writing";
+my $guard = uc($output_h);
+$guard =~ s/[^A-Z]/__/g;
+print H <<__E;
+// automatically generated file, do not edit
+
+#ifndef $guard
+#define $guard
+class DatabaseConnection;
+
+void ${fn_base_name}_Create(DatabaseConnection &rConnection);
+void ${fn_base_name}_Drop(DatabaseConnection &rConnection);
+
+#endif // $guard
+
+__E
+
+
+# finish up
+close H;


Property changes on: box/features/codeforintegration/lib/database/makedbcreate.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/makedbmake.pl
===================================================================
--- box/features/codeforintegration/lib/database/makedbmake.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/database/makedbmake.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+# Usage: makedbmake.pl directory
+
+use strict;
+
+my ($dir) = @ARGV;
+unless(-d $dir)
+{
+	die "Directory $dir not found"
+}
+
+print "Making makefile for database autogen in $dir\n";
+
+# make output directory
+my $output_dir = "$dir/autogen_db";
+unless(-d $output_dir)
+{
+	mkdir $output_dir,0755
+		or die "Can't create directory $output_dir"
+}
+
+# go through entries in this file, writing database output lines
+opendir DIR,$dir or die "Can't open $dir for reading";
+my @items = readdir DIR;
+close DIR;
+
+open MAKE,">$dir/Makefile.db" or die "Can't open makefile for writing";
+
+for my $item (@items)
+{
+	next unless $item =~ m/\.(schema|query|cpp)\Z/;
+	
+	my $input_filename = "$dir/$item";
+	my $output_h = '';
+	my $output_cpp = '';
+	
+	if($item =~ m/\A(.+?)\.schema\Z/)
+	{
+		my $o_cpp = "autogen_db/$1_schema.cpp";
+		my $o_h = "autogen_db/$1_schema.h";
+		my $cmd = '../../lib/database/makedbcreate.pl '.$item.' '.$o_cpp.' '.$o_h;
+		print MAKE "$o_cpp $o_h:\t$item\n\t$cmd\n\n";
+		die "Running command $cmd failed" unless system("(cd $dir; $cmd)") == 0;
+	}
+	elsif($item =~ m/\A(.+?)\.query\Z/)
+	{
+		# query file, easy
+		$output_h = "autogen_db/$1.h";
+		$output_cpp = "autogen_db/$1.cpp";
+	}
+	elsif($item =~ m/\A(.+?)\.cpp\Z/)
+	{
+		# not quite so easy, have to see if it contains any queries
+		my $fr = $1;
+		open FL,$input_filename or die "Can't open $input_filename for reading";
+		my $c;
+		read FL,$c,-s $input_filename;
+		close FL;
+		if($c =~ /\r?\n?\s*SQL\s+Query\s*[\r\n]+\s*\[\s*[\r\n]+.+?[\r\n]+\s*\]\s*[\r\n]/sg)
+		{
+			# yes...
+			$output_h = "autogen_db/${fr}_query.h";
+			$output_cpp = "autogen_db/${fr}_query.cpp";
+		}
+	}
+	
+	# if output is necessary, generate the makefile line and run the program
+	if($output_h ne '')
+	{
+		my $cmd = '../../lib/database/makequeries.pl '.$item.' '.$output_cpp.' '.$output_h;
+		print MAKE "$output_cpp $output_h:\t$item\n\t$cmd\n\n";
+		die "Running command $cmd failed" unless system("(cd $dir; $cmd)") == 0;
+	}
+}
+
+close MAKE;
+


Property changes on: box/features/codeforintegration/lib/database/makedbmake.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/database/makequeries.pl
===================================================================
--- box/features/codeforintegration/lib/database/makequeries.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/database/makequeries.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,107 @@
+#!/usr/bin/perl
+
+# Usage: makequeries.pl inputfile output.cpp output.h
+
+use strict;
+use lib '../../lib/perl';
+use lib '../../lib/database';
+use Database::Query;
+
+my ($input_file, $output_cpp, $output_h) = @ARGV;
+if(!-f $input_file)
+{
+	die "Input file $input_file not found";
+}
+if($output_cpp eq '' || $output_h eq '')
+{
+	die "Output files not specified";
+}
+
+# read in input
+open IN,$input_file or die "Can't open $input_file";
+my $input;
+read IN,$input,-s $input_file;
+close IN;
+
+# open output files
+open CPP,'>'.$output_cpp or die "Can't open $output_cpp for writing";
+open H,'>'.$output_h or die "Can't open $output_h for writing";
+
+# write boilerplate
+$output_h =~ m~([^/]+)\Z~;
+my $h_include = $1;
+print CPP <<__E;
+// automatically generated file, do not edit
+
+#include "Box.h"
+#include "$h_include"
+#include "DatabaseConnection.h"
+#include "MemLeakFindOn.h"
+__E
+my $guard = uc($output_h);
+$guard =~ s/[^A-Z]/__/g;
+print H <<__E;
+// automatically generated file, do not edit
+
+#ifndef $guard
+#define $guard
+#include "DatabaseQuery.h"
+__E
+
+# some queries will need DatabaseQueryGeneric.h included
+my $generic_header_included = 0;
+
+# go through input file, finding queries and writing things out
+while($input =~ /\r?\n?\s*SQL\s+Query\s*[\r\n]+\s*\[\s*[\r\n]+(.+?)[\r\n]+\s*\]\s*[\r\n]/sg)
+{
+	my $defn = $1;
+	#print $defn,"\n";
+	my %attr;
+	my $cattr = '';
+	for(split /[\r\n]+/,$defn)
+	{
+		s/\A\s+//; s/\s+\Z//;
+		if(m/\A(\w+):\s*(.+)\Z/)
+		{
+			$cattr = $1;
+			die "Attribute $cattr already defined in query" if exists $attr{$cattr};
+			$attr{$cattr} = $2;
+		}
+		else
+		{
+			if($cattr ne '')
+			{
+				$attr{$cattr} .= ' '.$_
+			}
+			else
+			{
+				die "First line of definition doesn't have an attribute name"
+			}
+		}
+	}
+	
+	# build a query object
+	my $query = Database::Query->new(%attr);
+	
+	# need to include the header file?
+	if($query->dervied_from_DatabaseQueryGeneric())
+	{
+		unless($generic_header_included)
+		{
+			$generic_header_included = 1;
+			print H '#include "DatabaseQueryGeneric.h"',"\n";
+		}
+	}
+	
+	# write the object out to the files
+	print CPP $query->generate_cpp();
+	print H $query->generate_h();
+}
+
+# finish up
+print H <<__E;
+#endif // $guard
+
+__E
+close H;
+close CPP;


Property changes on: box/features/codeforintegration/lib/database/makequeries.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdriver/DatabaseDriver.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdriver/DatabaseDriver.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/DatabaseDriver.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,178 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseDriver.cpp
+//		Purpose: Database driver interface
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <syslog.h>
+
+#include "DatabaseDriver.h"
+#include "autogen_DatabaseException.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDriver::DatabaseDriver()
+//		Purpose: Constructor
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDriver::DatabaseDriver()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDriver::~DatabaseDriver()
+//		Purpose: Destructor
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDriver::~DatabaseDriver()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDriver::ReportErrorMessage(const char *)
+//		Purpose: Report an error message from a driver
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseDriver::ReportErrorMessage(const char *ErrorMessage)
+{
+	if(ErrorMessage != 0)
+	{
+		TRACE2("%s error: %s\n", GetDriverName(), ErrorMessage);
+		::syslog(LOG_ERR, "%s error: %s\n", GetDriverName(), ErrorMessage);
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDriver::TranslateGeneric(int, const char **, int *, std::string &)
+//		Purpose: Translate a generic represetation (overrideable)
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+void DatabaseDriver::TranslateGeneric(int NumberElements, const char **Element, int *ElementLength, std::string &rOut)
+{
+	const TranslateMap_t &translate(GetGenericTranslations());
+	
+	// Find the element in the map
+	std::string nm(Element[0], ElementLength[0]);
+	if(NumberElements > 1)
+	{
+		nm += '0' + (NumberElements - 1);
+	}
+
+	// Find the corresponding string
+	TranslateMap_t::const_iterator e(translate.find(nm));
+	if(e == translate.end())
+	{
+		// Not found
+		THROW_EXCEPTION(DatabaseException, GenericNotKnownByDriver)
+	}
+
+	// Output it!
+	if(NumberElements <= 1)
+	{
+		// Just copy into output, don't bother with looking for arguments
+		rOut = e->second;
+		return;
+	}
+	
+	// Copy into output, adding in elements where required
+	int b = 0;
+	int p = 0;
+	rOut = std::string(); // ie clear()
+	const char *t = e->second.c_str();
+	int t_size = e->second.size();
+	while(p < t_size)
+	{
+		if(t[p] == '!')
+		{
+			// Output string so far
+			if(b != p)
+			{
+				rOut.append(t + b, p - b);
+			}
+			
+			// Find argument number
+			++p;
+			int a = t[p] - '0';
+			if(a < 0 || a > NumberElements)
+			{
+				THROW_EXCEPTION(DatabaseException, BadGenericTranslation)
+			}
+			// ... and append the argument value
+			rOut.append(Element[a + 1], ElementLength[a + 1]);
+			
+			// Set new beginning
+			b = p + 1;
+		}
+		++p;
+	}
+	// Anything remaining
+	if(b != p)
+	{
+		rOut.append(t + b, p - b);
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDriver::GetGenericTranslations()
+//		Purpose: Return the map of generic translations for this driver
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+const DatabaseDriver::TranslateMap_t &DatabaseDriver::GetGenericTranslations()
+{
+	// Default implementation just returns a map containing nothing
+	static const TranslateMap_t nullMap;
+	return nullMap;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDrvQuery::DatabaseDrvQuery()
+//		Purpose: Constructor
+//		Created: 7/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDrvQuery::DatabaseDrvQuery()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DatabaseDrvQuery::~DatabaseDrvQuery()
+//		Purpose: Destructor
+//		Created: 7/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDrvQuery::~DatabaseDrvQuery()
+{
+}
+


Property changes on: box/features/codeforintegration/lib/dbdriver/DatabaseDriver.cpp
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdriver/DatabaseDriver.h
===================================================================
--- box/features/codeforintegration/lib/dbdriver/DatabaseDriver.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/DatabaseDriver.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,104 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseDriver.h
+//		Purpose: Database driver interface
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASEDRIVER__H
+#define DATABASEDRIVER__H
+
+#include <string>
+#include <map>
+
+#include "DatabaseTypes.h"
+
+class DatabaseDrvQuery;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DatabaseDriver
+//		Purpose: Database driver interface
+//		Created: 2/5/04
+//
+// --------------------------------------------------------------------------
+class DatabaseDriver
+{
+public:
+	DatabaseDriver();
+	virtual ~DatabaseDriver();
+private:
+	// no copying
+	DatabaseDriver(const DatabaseDriver &);
+	DatabaseDriver &operator=(const DatabaseDriver &);
+public:
+
+	virtual const char *GetDriverName() const = 0;
+
+	virtual void Connect(const std::string &rConnectionString, int Timeout) = 0;
+	virtual DatabaseDrvQuery *Query() = 0;
+	virtual void Disconnect() = 0;
+
+	// Info
+	virtual int32_t GetLastAutoIncrementValue(const char *TableName, const char *ColumnName) = 0;
+
+	// Utility
+	virtual void QuoteString(const char *pString, std::string &rStringQuotedOut) const = 0;
+	
+	// Vendorisation
+	virtual void TranslateGeneric(int NumberElements, const char **Element, int *ElementLength, std::string &rOut);
+	typedef std::map<std::string, std::string> TranslateMap_t;
+	virtual const TranslateMap_t &GetGenericTranslations();
+
+	// Error reporting. Not overrideable.
+	void ReportErrorMessage(const char *ErrorMessage);
+};
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DatabaseDrvQuery
+//		Purpose: Driver interface to query
+//		Created: 7/5/04
+//
+// --------------------------------------------------------------------------
+class DatabaseDrvQuery
+{
+public:
+	DatabaseDrvQuery();
+	virtual ~DatabaseDrvQuery();
+private:
+	// no copying
+	DatabaseDrvQuery(const DatabaseDrvQuery &);
+	DatabaseDrvQuery &operator=(const DatabaseDrvQuery &);
+public:
+
+	virtual void Execute(const char *SQLStatement, int NumberParameters,
+		const Database::FieldType_t *pParameterTypes, const void **pParameters) = 0;
+
+	virtual int GetNumberChanges() const = 0;
+	virtual int GetNumberRows() const = 0;
+	virtual int GetNumberColumns() const = 0;
+
+	virtual bool Next() = 0;
+	virtual bool HaveRow() const = 0;
+
+	virtual void Finish() = 0;
+
+	virtual bool IsFieldNull(int Column) const = 0;
+
+	virtual int32_t GetFieldInt(int Column) const = 0;
+	virtual void GetFieldString(int Column, std::string &rStringOut) const = 0;
+};
+
+
+#define DATABASE_DRIVER_FILL_TRANSLATION_TABLE(table, fromI, toI)					\
+	const char **fromS = fromI; const char **toS = toI;								\
+	if(table.size() == 0) {while(*fromS != 0) {table[std::string(*(fromS++))] = std::string(*(toS++));}}
+
+#endif // DATABASEDRIVER__H
+


Property changes on: box/features/codeforintegration/lib/dbdriver/DatabaseDriver.h
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdriver/DatabaseException.txt
===================================================================
--- box/features/codeforintegration/lib/dbdriver/DatabaseException.txt	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/DatabaseException.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,32 @@
+EXCEPTION Database 14
+
+Internal						0
+DriverNotAvailable				1	Driver name incorrect, or driver not compiled in
+QueryNotExecuted				2	Need to execute the query first
+NotConnected					3
+AlreadyConnected				4
+BadQueryParameters				5
+FailedToConnect					6
+BadSQLStatement					7
+AttemptToMoveBeyondQueryEnd		8
+NoRowAvailable					9
+ColumnOutOfRange				10
+TooManyParametersToExecute		11	DatabaseQueryGeneric::Execute(types, ...) has too many arguments
+UnknownTypeCharacter			12	DatabaseQueryGeneric::Execute(types, ...) was given an unknown type char in the types string
+BadInsertationMarker			13	Check insertation markers ($n, n = 1 ...)
+InsertationMarkerOutOfRange		14	Marker doesn't have corresponding parameter
+InsertationMarkersNotInOrder	15	For compatibility with multiple database servers, insertation markers must be specified in order
+UnknownValueType				16	An unknown value type was encountered
+MustCallNextBeforeReadingData	17	Data cannot be read from a query until Next() has been called
+QueryContainedMoreThanSingleValue	18	Check that the query will only return one value
+BadConnectionString				19
+ErrorExecutingSQL				20
+UnexpectedLibraryBehaviour		21	The library behaved unexpectedly when processing a request
+TooManyArgumentsToGeneric		22	A generic representaiton in a query string has too many arguments
+GenericNotKnownByDriver			23	A generic representation used in a query string was not recognised by a driver
+BadGenericTranslation			24	A driver provided a bad translation string for a generic
+FailedToRetrieveAutoIncValue	25	A driver couldn't retrieve the auto increment value
+TooManyParameters				26	There is a compiled in limit on the number of parameters to a query -- adjust in DatabaseTypes.h if necessary.
+PostgreSQLUnhandledBinaryType	27	A value was retrieved by the PostgreSQL driver, but it was in a binary format which could not be parsed. Check the column has the correct type in your query definition.
+IntegerOverflow					28	An integer value was returned by a database which was too large to fit in the requested integer return type.
+PostgreSQLBadConversionFromNumericType	29	A numeric type returned by PostgreSQL overflowed, or there was another problem converting it to int.


Property changes on: box/features/codeforintegration/lib/dbdriver/DatabaseException.txt
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdriver/DatabaseTypes.h
===================================================================
--- box/features/codeforintegration/lib/dbdriver/DatabaseTypes.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/DatabaseTypes.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,37 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DatabaseTypes.h
+//		Purpose: Types for database drivers
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASETYPES__H
+#define DATABASETYPES__H
+
+
+// Declarations of types
+namespace Database
+{
+	enum
+	{
+		// Note: These values are chosen to match PostgreSQL OID values
+		Type_String = 25, // TEXTOID
+		Type_Int32 = 23, // INT4OID
+		Type_Int16 = 21 // INT2OID
+//		Type_Int8 = 3  // PostgreSQL doesn't have such a type, work round this later if this type is required.
+	};
+	
+	typedef int32_t FieldType_t;
+	
+	// Maximum number of parameters allowed for a database query
+	enum
+	{
+		MaxParameters = 64
+	};
+};
+
+
+#endif // DATABASETYPES__H
+

Added: box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,171 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverInsertParameters.cpp
+//		Purpose: Utility function for drivers which can't insert parameters with the native API
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "DbDriverInsertParameters.h"
+#include "DatabaseDriver.h"
+#include "autogen_DatabaseException.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverInsertParameters(const char *, int, const Database::FieldType_t *, const void **, const DatabaseDriver &, std::string &)
+//		Purpose: Utility function for drivers which can't insert parameters with the native API
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverInsertParameters(const char *SQLStatement, int NumberParameters, const Database::FieldType_t *pParameterTypes,
+	const void **pParameters, const DatabaseDriver &rDriver, std::string &rStatementOut)
+{
+	ASSERT(SQLStatement != 0);
+
+	// Output string
+	std::string result;
+
+	// Copy in bits of the string, and add in the parameters where marked
+	int lastParameter = -1;
+	int s = 0;
+	int p = 0;
+	char quotes = 0;
+	while(SQLStatement[p] != '\0')
+	{
+		switch(SQLStatement[p])
+		{
+		case '\'':
+		case '"':
+			if(quotes == SQLStatement[p])
+			{
+				// End of quotes
+				quotes = 0;
+			}
+			else
+			{
+				// Beginning of quotes
+				quotes = SQLStatement[p];
+			}
+			break;
+		
+		case '$': // Insertation marker
+			{
+				// Store last bit of string
+				if(s != p)
+				{
+					result.append(SQLStatement + s, p - s);
+				}
+				// Move to next character
+				++p;
+
+				// First character
+				int num = 0;
+				if(SQLStatement[p] >= '0' && SQLStatement[p] <= '9')
+				{
+					num = SQLStatement[p] - '0';
+				}
+				else
+				{
+					THROW_EXCEPTION(DatabaseException, BadInsertationMarker)
+				}
+				++p;
+				
+				// Is a second character present?
+				if(SQLStatement[p] >= '0' && SQLStatement[p] <= '9')
+				{
+					num *= 10;
+					num += SQLStatement[p] - '0';
+					++p;
+				}
+
+				// The number specified is 1-based, change to zero based
+				--num;
+
+				// Check the parameter is in range
+				if(num < 0 || num >= NumberParameters)
+				{
+					THROW_EXCEPTION(DatabaseException, InsertationMarkerOutOfRange)
+				}
+
+				// Check the parameter is in order
+				if(num <= lastParameter)
+				{
+					THROW_EXCEPTION(DatabaseException, InsertationMarkersNotInOrder)
+				}
+
+				// Convert the parameter
+				if(pParameters[num] == NULL)
+				{
+					// Null is handled differently, as is allowed for all types
+					result += "NULL";
+				}
+				else
+				{
+					if(pParameterTypes[num] == Database::Type_String)
+					{
+						// String
+						std::string quoted;
+						rDriver.QuoteString((const char *)pParameters[num], quoted);
+						result += quoted;
+					}
+					else
+					{
+						// Integer type -- for now don't support anything other than strings and ints
+						int32_t value = 0;
+						
+						switch(pParameterTypes[num])
+						{
+						case Database::Type_Int32:
+							value = *((int32_t*)(pParameters[num]));
+							break;
+						case Database::Type_Int16:
+							value = *((int16_t*)(pParameters[num]));
+							break;
+					// -- removed because PostgreSQL doesn't support this type natively.
+					//	case Database::Type_Int8:
+					//		value = *((int8_t*)(pParameters[num]));
+					//		break;
+						default:
+							THROW_EXCEPTION(DatabaseException, UnknownValueType)
+							break;
+						}
+						// Add value to SQL statement
+						char t[32];
+						::sprintf(t, "%d", value);
+						result += t;
+					}
+				}
+
+				// Next bit!
+				s = p;
+				lastParameter = num;
+				continue;
+			}
+			break;
+		
+		default:
+			// A normal character, it looks lovely
+			break;
+		}
+		// Next!
+		++p;
+	}
+	// Add in the rest of the statement
+	if(s != p)
+	{
+		result.append(SQLStatement + s, p - s);
+	}
+
+	// Return the result
+	rStatementOut = result;
+}
+

Added: box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.h
===================================================================
--- box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/DbDriverInsertParameters.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,21 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverInsertParameters.cpp
+//		Purpose: Utility function for drivers which can't insert parameters with the native API
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBDRIVERINSERTPARAMETERS__H
+#define DBDRIVERINSERTPARAMETERS__H
+
+#include <string>
+#include "DatabaseTypes.h"
+class DatabaseDriver;
+
+void DbDriverInsertParameters(const char *SQLStatement, int NumberParameters, const Database::FieldType_t *pParameterTypes,
+	const void **pParameters, const DatabaseDriver &rDriver, std::string &rStatementOut);
+
+#endif // DBDRIVERINSERTPARAMETERS__H
+

Added: box/features/codeforintegration/lib/dbdriver/Makefile.extra
===================================================================
--- box/features/codeforintegration/lib/dbdriver/Makefile.extra	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdriver/Makefile.extra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,6 @@
+
+MAKEEXCEPTION = ../../lib/common/makeexception.pl
+
+# AUTOGEN SEEDING
+autogen_DatabaseException.h autogen_DatabaseException.cpp:	$(MAKEEXCEPTION) DatabaseException.txt
+	perl $(MAKEEXCEPTION) DatabaseException.txt


Property changes on: box/features/codeforintegration/lib/dbdriver/Makefile.extra
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,282 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverMySQL.cpp
+//		Purpose: Database driver for MySQL
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <memory>
+
+#include "DbDriverMySQL.h"
+#include "DbQueryMySQL.h"
+#include "autogen_DatabaseException.h"
+#include "Utils.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::DbDriverMySQL()
+//		Purpose: Constructor
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+DbDriverMySQL::DbDriverMySQL()
+	: mpConnection(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::~DbDriverMySQL()
+//		Purpose: Destructor
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+DbDriverMySQL::~DbDriverMySQL()
+{
+	if(mpConnection != 0)
+	{
+		Disconnect();
+	}
+	ASSERT(mpConnection == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::GetDriverName()
+//		Purpose: Name of driver
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+const char *DbDriverMySQL::GetDriverName() const
+{
+	return "mysql";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::Connect(const std::string &, int)
+//		Purpose: Connect to database, Connection string is database:username:password, or
+//				 hostname:database:username:password
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverMySQL::Connect(const std::string &rConnectionString, int Timeout)
+{
+	if(mpConnection != 0)
+	{
+		THROW_EXCEPTION(DatabaseException, AlreadyConnected)
+	}
+	
+	// Decode the connection string
+	std::vector<std::string> elements;
+	SplitString(rConnectionString, ':', elements);
+	const char *hostname = 0;
+	int ps = 0;
+	switch(elements.size())
+	{
+	case 3:
+		ps = 0;
+		break;
+	case 4:
+		hostname = elements[0].c_str();
+		ps = 1;
+		break;
+	default:
+		THROW_EXCEPTION(DatabaseException, BadConnectionString)
+		break;
+	}
+	const char *database = elements[ps+0].c_str();
+	const char *username = elements[ps+1].c_str();
+	const char *password = elements[ps+2].c_str();
+	if(hostname != 0 && hostname[0] == '\0') hostname = NULL;
+	if(database[0] == '\0') database = NULL;
+	if(username[0] == '\0') username = NULL;
+	if(password[0] == '\0') password = NULL;
+	
+
+	// Allocate a connection object
+	MYSQL *pconnection = ::mysql_init(NULL);
+	if(pconnection == NULL)
+	{
+		THROW_EXCEPTION(DatabaseException, FailedToConnect)
+	}
+
+	try
+	{
+		// Set connect timeout
+		unsigned int timeout = (Timeout + 999) / 1000;	// in seconds, rounded up
+		::mysql_options(pconnection, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)(&timeout));
+		
+		// Connect!
+		if(::mysql_real_connect(pconnection, hostname, username, password, database, 0, NULL, 0) == NULL)
+		{
+			ReportMySQLError(pconnection);
+			THROW_EXCEPTION(DatabaseException, FailedToConnect)
+		}
+	}
+	catch(...)
+	{
+		::mysql_close(pconnection);
+		throw;
+	}
+	
+	// Store connection
+	mpConnection = pconnection;		
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::ReportMySQLError(MYSQL *)
+//		Purpose: Report the error from the mysql interface. If arg is zero, then use current connection.
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverMySQL::ReportMySQLError(MYSQL *pConnection)
+{
+	MYSQL *pconn = pConnection;
+	if(pconn == NULL)
+	{
+		pconn = mpConnection;
+	}
+
+	if(pconn != 0)
+	{
+		ReportErrorMessage(::mysql_error(pconn));
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::Query()
+//		Purpose: Returns a new query object
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDrvQuery *DbDriverMySQL::Query()
+{
+	return new DbQueryMySQL(*this);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::Disconnect()
+//		Purpose: Disconnect from the database
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverMySQL::Disconnect()
+{
+	if(mpConnection == 0)
+	{
+		// Nothing to do
+		return;
+	}
+
+	// Close connection to database
+	::mysql_close(mpConnection);
+	mpConnection = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::QuoteString(const char *, std::string &)
+//		Purpose: Quote a string ready for inclusion into some nice SQL
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverMySQL::QuoteString(const char *pString, std::string &rStringQuotedOut) const
+{
+	ASSERT(pString != 0);
+
+	// Not an amazingly efficient interface, but there you go
+	unsigned long stringLength = ::strlen(pString);
+	unsigned long bufferSize = (stringLength * 2) + 6;	// calcuation from MySQL docs, with extra space for quotes
+	char *buffer = (char *)::malloc(bufferSize);
+	if(buffer == NULL)
+	{
+		throw std::bad_alloc();
+	}
+	
+	// Initial quote char (MySQL doesn't do the quote chars for us)
+	buffer[0] = '\'';
+	
+	// Quote string
+	unsigned long len = ::mysql_real_escape_string(mpConnection, buffer + 1, pString, stringLength);
+
+	// Add final quote char and terminator
+	buffer[len + 1] = '\'';
+	buffer[len + 2] = '\0';
+
+	// Copy into output string
+	try
+	{
+		rStringQuotedOut.assign(buffer, len + 2);
+	}
+	catch(...)
+	{
+		::free(buffer);
+		throw;
+	}
+
+	// Free temporary output block
+	::free(buffer);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::GetLastAutoIncrementValue(const char *, const char *)
+//		Purpose: Get the last inserted value
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+int32_t DbDriverMySQL::GetLastAutoIncrementValue(const char *TableName, const char *ColumnName)
+{
+	my_ulonglong id = ::mysql_insert_id(mpConnection);
+	return (int32_t)id;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::GetGenericTranslations()
+//		Purpose: Get translations for generics in SQL statements
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+const DatabaseDriver::TranslateMap_t &DbDriverMySQL::GetGenericTranslations()
+{
+	static DatabaseDriver::TranslateMap_t table;
+	const char *from[] = {"AUTO_INCREMENT_INT", "LIMIT2", "CREATE_INDEX_CASE_INSENSTIVE3", "COLUMN_CASE_INSENSITIVE_ORDERING", "CASE_INSENSITIVE_COLUMN1", 0};
+	const char *to[] = {"INT NOT NULL PRIMARY KEY AUTO_INCREMENT", "LIMIT !0,!1", "CREATE INDEX !0 ON !1 (!2)", "", "!0", 0};
+	DATABASE_DRIVER_FILL_TRANSLATION_TABLE(table, from, to);
+	return table;
+}
+
+

Added: box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_mysql/DbDriverMySQL.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,62 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverMySQL.h
+//		Purpose: Database driver for MySQL
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBDRIVERMYSQL__H
+#define DBDRIVERMYSQL__H
+
+#include "mysql/mysql.h"
+
+#include "DatabaseDriver.h"
+
+class DbQueryMySQL;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DbDriverMySQL
+//		Purpose: Database driver for MySQL
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+class DbDriverMySQL : public DatabaseDriver
+{
+	friend class DbQueryMySQL;
+public:
+	DbDriverMySQL();
+	virtual ~DbDriverMySQL();
+private:
+	// no copying
+	DbDriverMySQL(const DbDriverMySQL &);
+	DbDriverMySQL &operator=(const DbDriverMySQL &);
+public:
+
+	virtual const char *GetDriverName() const;
+
+	virtual void Connect(const std::string &rConnectionString, int Timeout);
+	virtual DatabaseDrvQuery *Query();
+	virtual void Disconnect();
+
+	virtual int32_t GetLastAutoIncrementValue(const char *TableName, const char *ColumnName);
+
+	virtual void QuoteString(const char *pString, std::string &rStringQuotedOut) const;
+
+	virtual const TranslateMap_t &GetGenericTranslations();
+
+protected:
+	// For query objects to get the open database file
+	MYSQL *GetMYSQL() const {return mpConnection;}
+	// For query objects to report error messages
+	void ReportMySQLError(MYSQL *pConnection = NULL);
+
+private:
+	MYSQL *mpConnection;
+};
+
+#endif // DBDRIVERMYSQL__H
+

Added: box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,417 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbQueryMySQL.cpp
+//		Purpose: Query object for MySQL driver
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+
+#include "DbQueryMySQL.h"
+#include "DbDriverMySQL.h"
+#include "autogen_DatabaseException.h"
+#include "Conversion.h"
+#include "DbDriverInsertParameters.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverMySQL::DbQueryMySQL()
+//		Purpose: Constructor
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+DbQueryMySQL::DbQueryMySQL(DbDriverMySQL &rDriver)
+	: mrDriver(rDriver),
+	  mpResults(0),
+	  mQueryReturnedData(true),
+	  mChangedRows(-1),
+	  mNumberRows(0),
+	  mNumberColumns(0),
+	  mFetchedFirstRow(false),
+	  mCurrentRow(NULL),
+	  mCurrentRowFieldLengths(NULL)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::~DbQueryMySQL()
+//		Purpose: Destructor
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+DbQueryMySQL::~DbQueryMySQL()
+{
+	if(HaveResults())
+	{
+		Finish();
+	}
+	ASSERT(mpResults == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    Execute(const char *, int, const Database::FieldType_t *, const void **)
+//		Purpose: Execute a query
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbQueryMySQL::Execute(const char *SQLStatement, int NumberParameters,
+	const Database::FieldType_t *pParameterTypes, const void **pParameters)
+{
+	if(HaveResults())
+	{
+		// Clean up the existing query object
+		Finish();
+	}
+
+	// If there are parameters, then they need to be inserted into the SQL manually
+	// by the driver -- there is no support in MySQL for doing it quite how we require.
+	int returnCode = -1;
+	if(NumberParameters != 0)
+	{
+		// Insert parameters, using the provided utility function
+		std::string sql;
+		DbDriverInsertParameters(SQLStatement, NumberParameters, pParameterTypes, pParameters, mrDriver, sql);
+		// Execute it
+		returnCode = ::mysql_real_query(mrDriver.GetMYSQL(), sql.c_str(), sql.size());
+	}
+	else
+	{
+		// Can just use this string
+		returnCode = ::mysql_real_query(mrDriver.GetMYSQL(), SQLStatement, ::strlen(SQLStatement));
+	}
+
+	// Worked?
+	if(returnCode != 0)
+	{
+		mrDriver.ReportMySQLError();
+		THROW_EXCEPTION(DatabaseException, BadSQLStatement)
+	}
+
+	// What kind of result is it?
+	MYSQL_RES *presults = ::mysql_store_result(mrDriver.GetMYSQL());
+	if(presults == NULL)
+	{
+		// No results returned -- should it have done?
+		if(::mysql_field_count(mrDriver.GetMYSQL()) == 0)
+		{
+			// No data should be returned (not a SELECT)
+			// Instead, find out how much data was returned
+			my_ulonglong r = ::mysql_affected_rows(mrDriver.GetMYSQL());
+			if(r == ((my_ulonglong)-1))
+			{
+				// Error
+				mrDriver.ReportMySQLError();
+				THROW_EXCEPTION(DatabaseException, ErrorExecutingSQL)
+			}
+			mQueryReturnedData = false;
+			mChangedRows = r;
+		}
+		else
+		{
+			// Data should have been returned
+			mrDriver.ReportMySQLError();
+			THROW_EXCEPTION(DatabaseException, ErrorExecutingSQL)
+		}
+	}
+	else
+	{
+		// Have results, store them
+		mQueryReturnedData = true;
+		mpResults = presults;
+		mFetchedFirstRow = false;
+		
+		// Find out a bit more data
+		mNumberColumns = ::mysql_num_fields(mpResults);
+		mNumberRows = ::mysql_num_rows(mpResults);
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::GetNumberChanges()
+//		Purpose: Return number of changes
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+int DbQueryMySQL::GetNumberChanges() const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	if(mQueryReturnedData)
+	{
+		// No rows changed
+		return 0;
+	}
+	
+	return mChangedRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::GetNumberRows()
+//		Purpose: Get number of rows
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+int DbQueryMySQL::GetNumberRows() const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::GetNumberColumns()
+//		Purpose: Get number of columns
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+int DbQueryMySQL::GetNumberColumns() const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mNumberColumns;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::Next()
+//		Purpose: Move cursor to next row
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+bool DbQueryMySQL::Next()
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Emulate zero rows behaviour for queries which didn't return data
+	if(!mFetchedFirstRow && !mQueryReturnedData)
+	{
+		mFetchedFirstRow = true;
+		return false;
+	}
+
+	// Check that a row should be fetched
+	if(mFetchedFirstRow && mCurrentRow == NULL)
+	{
+		THROW_EXCEPTION(DatabaseException, AttemptToMoveBeyondQueryEnd)
+	}
+
+	ASSERT(mpResults != NULL);
+
+	// Try to get a row
+	mCurrentRow = ::mysql_fetch_row(mpResults);
+	mFetchedFirstRow = true;
+
+	// If a row was returned, get the lengths of all the fields returned
+	if(mCurrentRow != NULL)
+	{
+		mCurrentRowFieldLengths = ::mysql_fetch_lengths(mpResults);
+		if(mCurrentRowFieldLengths == NULL)
+		{
+			THROW_EXCEPTION(DatabaseException, UnexpectedLibraryBehaviour)
+		}
+	}
+
+	// Got a row?
+	return mCurrentRow != NULL;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::HaveRow()
+//		Purpose: Row available?
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+bool DbQueryMySQL::HaveRow() const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mCurrentRow != NULL;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::Finish()
+//		Purpose: Finish with this query
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbQueryMySQL::Finish()
+{
+	// Check that this call is appropraite
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Free results?
+	if(mpResults != 0)
+	{
+		::mysql_free_result(mpResults);
+		mpResults = 0;
+	}
+
+	// Reset data
+	mpResults = 0;
+	mQueryReturnedData = true;
+	mChangedRows = -1;
+	mNumberRows = 0;
+	mNumberColumns = 0;
+	mFetchedFirstRow = false;
+	mCurrentRow = NULL;
+	mCurrentRowFieldLengths = NULL;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::IsFieldNull(int)
+//		Purpose: Is the field null?
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+bool DbQueryMySQL::IsFieldNull(int Column) const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == NULL && !mFetchedFirstRow)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow == NULL)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Simple check
+	return mCurrentRow[Column] == NULL;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::GetFieldInt(int)
+//		Purpose: Get a field's value, as int
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+int32_t DbQueryMySQL::GetFieldInt(int Column) const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == NULL && !mFetchedFirstRow)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow == NULL)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Do something vaguely sensible with null values
+	if(mCurrentRow[Column] == NULL)
+	{
+		return 0;
+	}
+
+	// Convert the number
+	return BoxConvert::Convert<int32_t, const char *>(mCurrentRow[Column]);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryMySQL::GetFieldString(int, std::string &)
+//		Purpose: Get a field's value, as string
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+void DbQueryMySQL::GetFieldString(int Column, std::string &rStringOut) const
+{
+	if(!HaveResults())
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == NULL && !mFetchedFirstRow)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow == NULL)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Do something vaguely sensible with null values
+	if(mCurrentRow[Column] == NULL)
+	{
+		rStringOut = std::string(); // ie clear();
+		return;
+	}
+
+	// Copy string into output string
+	rStringOut.assign(mCurrentRow[Column], mCurrentRowFieldLengths[Column]);
+}
+

Added: box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_mysql/DbQueryMySQL.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,74 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbQueryMySQL.h
+//		Purpose: Query object for MySQL driver
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBQUERYMYSQL__H
+#define DBQUERYMYSQL__H
+
+#include "mysql/mysql.h"
+
+#include "DatabaseDriver.h"
+
+class DbDriverMySQL;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DbQueryMySQL
+//		Purpose: Query object for MySQL driver
+//		Created: 11/5/04
+//
+// --------------------------------------------------------------------------
+class DbQueryMySQL : public DatabaseDrvQuery
+{
+public:
+	DbQueryMySQL(DbDriverMySQL &rDriver);
+	~DbQueryMySQL();
+private:
+	// no copying
+	DbQueryMySQL(const DbQueryMySQL &);
+	DbQueryMySQL &operator=(const DbQueryMySQL &);
+public:
+
+	virtual void Execute(const char *SQLStatement, int NumberParameters,
+		const Database::FieldType_t *pParameterTypes, const void **pParameters);
+
+	virtual int GetNumberChanges() const;
+	virtual int GetNumberRows() const;
+	virtual int GetNumberColumns() const;
+
+	virtual bool Next();
+	virtual bool HaveRow() const;
+
+	virtual void Finish();
+
+	virtual bool IsFieldNull(int Column) const;
+
+	virtual int32_t GetFieldInt(int Column) const;
+	virtual void GetFieldString(int Column, std::string &rStringOut) const;
+
+protected:
+	inline bool HaveResults() const
+	{
+		return mQueryReturnedData?(mpResults != 0):(mChangedRows >= 0);
+	}
+
+private:
+	DbDriverMySQL &mrDriver;
+	MYSQL_RES *mpResults;
+	bool mQueryReturnedData;
+	int64_t mChangedRows;
+	int mNumberRows;
+	int mNumberColumns;
+	bool mFetchedFirstRow;
+	MYSQL_ROW mCurrentRow;
+	unsigned long *mCurrentRowFieldLengths;
+};
+
+#endif // DBQUERYMYSQL__H
+

Added: box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,288 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverPostgreSQL.cpp
+//		Purpose: Database driver for PostgreSQL
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory>
+
+#include "DbDriverPostgreSQL.h"
+#include "DbQueryPostgreSQL.h"
+#include "autogen_DatabaseException.h"
+#include "Utils.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    static void boxPgNoticeProcessor(void *, const char *)
+//		Purpose: Send PostgreSQL notices to TRACE
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+static void boxPgNoticeProcessor(void *arg, const char *message)
+{
+    TRACE1("PG: %s", message);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::DbDriverPostgreSQL()
+//		Purpose: Constructor
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+DbDriverPostgreSQL::DbDriverPostgreSQL()
+	: mpConnection(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::~DbDriverPostgreSQL()
+//		Purpose: Destructor
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+DbDriverPostgreSQL::~DbDriverPostgreSQL()
+{
+	if(mpConnection != 0)
+	{
+		Disconnect();
+	}
+	ASSERT(mpConnection == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::GetDriverName()
+//		Purpose: Name of driver
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+const char *DbDriverPostgreSQL::GetDriverName() const
+{
+	return "postgresql";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::Connect(const std::string &, int)
+//		Purpose: Connect to database, Connection string is as defined by libpq library
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbDriverPostgreSQL::Connect(const std::string &rConnectionString, int Timeout)
+{
+	if(mpConnection != 0)
+	{
+		THROW_EXCEPTION(DatabaseException, AlreadyConnected)
+	}
+
+	// Generate the connection info string
+	std::string conninfo(rConnectionString);
+	// If there isn't a timeout specified in the string, add it
+	if(conninfo.find("connect_timeout") == std::string::npos)
+	{
+		char timeout[64];
+		::sprintf(timeout, " connect_timeout = %d", (Timeout + 999) / 1000);	// in seconds, rounded up
+		conninfo += timeout;
+	}
+	TRACE1("DbDriverPostgesSQL, using conninfo = '%s'\n", conninfo.c_str());
+
+	// Allocate a connection object
+	PGconn *pconnection = ::PQconnectdb(rConnectionString.c_str());
+	if(pconnection == NULL)
+	{
+		THROW_EXCEPTION(DatabaseException, FailedToConnect)
+	}
+
+	// Set nofity processor to send notices to TRACE
+	::PQsetNoticeProcessor(pconnection, boxPgNoticeProcessor, 0);
+
+	// Check connection worked OK
+	if(::PQstatus(pconnection) != CONNECTION_OK)
+	{
+		ReportPostgreSQLError(pconnection);
+		::PQfinish(pconnection);
+		THROW_EXCEPTION(DatabaseException, FailedToConnect)
+	}
+
+	// Store connection
+	mpConnection = pconnection;		
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::ReportPostgreSQLError(PGconn *)
+//		Purpose: Report the error from the mysql interface. If arg is zero, then use current connection.
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbDriverPostgreSQL::ReportPostgreSQLError(PGconn *pConnection)
+{
+	PGconn *pconn = pConnection;
+	if(pconn == NULL)
+	{
+		pconn = mpConnection;
+	}
+
+	if(pconn != 0)
+	{
+		ReportErrorMessage(::PQerrorMessage(pconn));
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::Query()
+//		Purpose: Returns a new query object
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+DatabaseDrvQuery *DbDriverPostgreSQL::Query()
+{
+	return new DbQueryPostgreSQL(*this);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::Disconnect()
+//		Purpose: Disconnect from the database
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbDriverPostgreSQL::Disconnect()
+{
+	if(mpConnection == 0)
+	{
+		// Nothing to do
+		return;
+	}
+
+	// Close connection to database
+	::PQfinish(mpConnection);
+	mpConnection = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::QuoteString(const char *, std::string &)
+//		Purpose: Quote a string ready for inclusion into some nice SQL
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbDriverPostgreSQL::QuoteString(const char *pString, std::string &rStringQuotedOut) const
+{
+	ASSERT(pString != 0);
+
+	// Not an amazingly efficient interface, but there you go
+	unsigned long stringLength = ::strlen(pString);
+	unsigned long bufferSize = (stringLength * 2) + 6;	// calcuation from PostgreSQL docs, with extra space for quotes
+	char *buffer = (char *)::malloc(bufferSize);
+	if(buffer == NULL)
+	{
+		throw std::bad_alloc();
+	}
+	
+	// Initial quote char (PostgreSQL doesn't do the quote chars for us)
+	buffer[0] = '\'';
+	
+	// Quote string
+	size_t len = ::PQescapeString(buffer + 1, pString, stringLength);
+
+	// Add final quote char and terminator
+	buffer[len + 1] = '\'';
+	buffer[len + 2] = '\0';
+
+	// Copy into output string
+	try
+	{
+		rStringQuotedOut.assign(buffer, len + 2);
+	}
+	catch(...)
+	{
+		::free(buffer);
+		throw;
+	}
+
+	// Free temporary output block
+	::free(buffer);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::GetLastAutoIncrementValue(const char *, const char *)
+//		Purpose: Get the last inserted value
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+int32_t DbDriverPostgreSQL::GetLastAutoIncrementValue(const char *TableName, const char *ColumnName)
+{
+	// Generate query to find the value by querying the appropraite sequence
+	std::string sql("SELECT currval('");
+	sql += TableName;
+	sql += '_';
+	sql += ColumnName;
+	sql += "_seq')";
+
+	// Run query
+	DbQueryPostgreSQL query(*this);
+	query.Execute(sql.c_str(), 0, NULL, NULL);
+	if(!query.Next())
+	{
+		THROW_EXCEPTION(DatabaseException, FailedToRetrieveAutoIncValue)
+	}
+	else
+	{
+		return query.GetFieldInt(0);
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::GetGenericTranslations()
+//		Purpose: Get translations for generics in SQL statements
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+const DatabaseDriver::TranslateMap_t &DbDriverPostgreSQL::GetGenericTranslations()
+{
+	static DatabaseDriver::TranslateMap_t table;
+	const char *from[] = {"AUTO_INCREMENT_INT", "LIMIT2", "CREATE_INDEX_CASE_INSENSTIVE3", "COLUMN_CASE_INSENSITIVE_ORDERING", "CASE_INSENSITIVE_COLUMN1", 0};
+	const char *to[] = {"SERIAL PRIMARY KEY", "LIMIT !1 OFFSET !0", "CREATE INDEX !0 ON !1 (LOWER(!2))", "", "LOWER(!0)", 0};
+	DATABASE_DRIVER_FILL_TRANSLATION_TABLE(table, from, to);
+	return table;
+}
+
+

Added: box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_postgresql/DbDriverPostgreSQL.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,62 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverPostgreSQL.h
+//		Purpose: Database driver for PostgreSQL
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBDRIVERPOSTGRESQL__H
+#define DBDRIVERPOSTGRESQL__H
+
+#include "postgresql/libpq-fe.h"
+
+#include "DatabaseDriver.h"
+
+class DbQueryPostgreSQL;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DbDriverPostgreSQL
+//		Purpose: Database driver for PostgreSQL
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+class DbDriverPostgreSQL : public DatabaseDriver
+{
+	friend class DbQueryPostgreSQL;
+public:
+	DbDriverPostgreSQL();
+	virtual ~DbDriverPostgreSQL();
+private:
+	// no copying
+	DbDriverPostgreSQL(const DbDriverPostgreSQL &);
+	DbDriverPostgreSQL &operator=(const DbDriverPostgreSQL &);
+public:
+
+	virtual const char *GetDriverName() const;
+
+	virtual void Connect(const std::string &rConnectionString, int Timeout);
+	virtual DatabaseDrvQuery *Query();
+	virtual void Disconnect();
+
+	virtual int32_t GetLastAutoIncrementValue(const char *TableName, const char *ColumnName);
+
+	virtual void QuoteString(const char *pString, std::string &rStringQuotedOut) const;
+
+	virtual const TranslateMap_t &GetGenericTranslations();
+
+protected:
+	// For query objects to get the open database file
+	PGconn *GetPGconn() const {return mpConnection;}
+	// For query objects to report error messages
+	void ReportPostgreSQLError(PGconn *pConnection = NULL);
+
+private:
+	PGconn *mpConnection;
+};
+
+#endif // DBDRIVERPOSTGRESQL__H
+

Added: box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,672 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbQueryPostgreSQL.cpp
+//		Purpose: Query object for PostgreSQL driver
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+#include <limits.h>
+#include <arpa/inet.h>
+
+#include "DbQueryPostgreSQL.h"
+#include "DbDriverPostgreSQL.h"
+#include "autogen_DatabaseException.h"
+#include "Conversion.h"
+#include "DbDriverInsertParameters.h"
+
+// Really, postgres/catalog/pg_type.h should be included, not this.
+// However, it's difficult to include on all platforms, so instead,
+// a file generated (by manually running script) from the header file.
+// OID types really shouldn't change, so this won't be a problem.
+// (Hopefully.) But the database test checks the types as well.
+#include "PostgreSQLOidTypes.h"
+
+// Get the numeric type definition and the conversion functions
+extern "C"
+{
+	#include "postgresql/pgtypes_numeric.h"
+}
+
+#include "MemLeakFindOn.h"
+
+
+// With the binary protocol, is byte swapping required?
+#ifndef PLATFORM_POSTGRESQL_OLD_API
+	#if BYTE_ORDER != BIG_ENDIAN
+		#define PG_BYTE_SWAPPING_REQUIRED
+	#endif
+#endif
+
+
+// PostgreSQL headers don't define constants like this
+#define FORMAT_TEXT		0
+#define FORMAT_BINARY	1
+
+// When using the new API, should results be requested in text or binary form?
+#ifdef POSTGRESQL_QUERY_RESULT_FORMAT
+	// Allow extra command line args to adjust behaviour
+	#define QUERY_RESULT_FORMAT		POSTGRESQL_QUERY_RESULT_FORMAT
+#else
+	// By default, use binary format
+	#define QUERY_RESULT_FORMAT		FORMAT_BINARY
+#endif
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverPostgreSQL::DbQueryPostgreSQL()
+//		Purpose: Constructor
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+DbQueryPostgreSQL::DbQueryPostgreSQL(DbDriverPostgreSQL &rDriver)
+	: mrDriver(rDriver),
+	  mpResults(0),
+	  mQueryReturnedData(true),
+	  mChangedRows(-1),
+	  mNumberRows(0),
+	  mNumberColumns(0),
+	  mCurrentRow(-1)
+{
+	// Check a few things are the right size and value
+	ASSERT(sizeof(Database::FieldType_t) == sizeof(Oid));
+	ASSERT(Database::Type_String == TEXTOID);
+	ASSERT(Database::Type_Int32 == INT4OID);
+	ASSERT(Database::Type_Int16 == INT2OID);
+#ifndef NDEBUG
+	static bool displayedConfig = false;
+	if(!displayedConfig)
+	{
+		displayedConfig = true;
+#ifdef PLATFORM_POSTGRESQL_OLD_API
+		TRACE0("DbQueryPostgreSQL configured to use old API\n");
+#else
+		TRACE1("DbQueryPostgreSQL configured to use new API with results in %s mode\n",
+				(QUERY_RESULT_FORMAT == FORMAT_BINARY)?"binary":"text");
+#endif
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+		TRACE0("DbQueryPostgreSQL byte swapping on binary data code enabled\n");
+#endif
+	}
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::~DbQueryPostgreSQL()
+//		Purpose: Destructor
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+DbQueryPostgreSQL::~DbQueryPostgreSQL()
+{
+	if(mpResults != 0)
+	{
+		Finish();
+	}
+	ASSERT(mpResults == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    Execute(const char *, int, const Database::FieldType_t *, const void **)
+//		Purpose: Execute a query
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbQueryPostgreSQL::Execute(const char *SQLStatement, int NumberParameters,
+	const Database::FieldType_t *pParameterTypes, const void **pParameters)
+{
+	if(mpResults != 0)
+	{
+		// Clean up the existing query object
+		Finish();
+	}
+
+	// Redundant check -- DatabaseQuery::Execute() will check this, but it's really important
+	// that this isn't exceeded here.
+	if(NumberParameters > Database::MaxParameters)
+	{
+		THROW_EXCEPTION(DatabaseException, TooManyParameters)
+	}
+
+	// TODO: Use PQexecParams instead!
+	PGresult *presults = NULL;
+	if(NumberParameters != 0)
+	{
+#ifdef PLATFORM_POSTGRESQL_OLD_API
+		// Insert parameters, using the provided utility function
+		std::string sql;
+		DbDriverInsertParameters(SQLStatement, NumberParameters, pParameterTypes, pParameters, mrDriver, sql);
+		// Execute it
+		presults = ::PQexec(mrDriver.GetPGconn(), sql.c_str());
+#else
+		// Build list of types
+		int paramLengths[Database::MaxParameters];
+		int paramFormats[Database::MaxParameters];
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+		const void *paramPointers[Database::MaxParameters];
+		int32_t paramIntegers[Database::MaxParameters];
+#endif
+		for(int p = 0; p < NumberParameters; ++p)
+		{
+			switch(pParameterTypes[p])
+			{
+			case Database::Type_String:
+				paramLengths[p] = 0;
+				paramFormats[p] = FORMAT_TEXT;
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+				// Just use supplied pointer
+				paramPointers[p] = pParameters[p];
+#endif
+				break;
+			case Database::Type_Int32:
+				paramLengths[p] = (pParameters[p] == NULL)?0:4;
+				paramFormats[p] = FORMAT_BINARY;
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+				// Copy integer, adjust pointer
+				{
+					int32_t i = *((int32_t*)(pParameters[p]));
+					paramIntegers[p] = htonl(i);
+					paramPointers[p] = paramIntegers + p;
+				}
+#endif
+				break;
+			case Database::Type_Int16:
+				paramLengths[p] = (pParameters[p] == NULL)?0:2;
+				paramFormats[p] = FORMAT_BINARY;
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+				// Copy integer, adjust pointer
+				{
+					int16_t i = *((int16_t*)(pParameters[p]));
+					int16_t *pint = (int16_t*)(paramIntegers + p);
+					*pint = htons(i);
+					paramPointers[p] = pint;
+				}
+#endif
+				break;
+			default:
+				THROW_EXCEPTION(DatabaseException, UnknownValueType)
+				break;
+			}
+		}
+
+		// Execute using binary protocol to insert parameters without having to quote strings
+		presults = ::PQexecParams(mrDriver.GetPGconn(), SQLStatement, NumberParameters,
+			(const Oid *)pParameterTypes,		// can do this, because the type numbers are chosen carefully
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+			(const char * const *)paramPointers,
+#else
+			(const char * const *)pParameters,
+#endif
+			paramLengths, paramFormats, QUERY_RESULT_FORMAT);
+#endif
+	}
+	else
+	{
+		// Can just use this string
+#ifdef PLATFORM_POSTGRESQL_OLD_API
+		presults = ::PQexec(mrDriver.GetPGconn(), SQLStatement);
+#else
+		presults = ::PQexecParams(mrDriver.GetPGconn(), SQLStatement, 0, NULL, NULL, NULL, NULL, QUERY_RESULT_FORMAT);
+#endif
+	}
+
+	// Worked?
+	if(presults == NULL)
+	{
+		mrDriver.ReportPostgreSQLError();
+		THROW_EXCEPTION(DatabaseException, ErrorExecutingSQL)
+	}
+	
+	// Check what actually happened
+	switch(::PQresultStatus(presults))
+	{
+	case PGRES_TUPLES_OK:
+		// Command returned some data
+		{
+			mQueryReturnedData = true;
+			mNumberColumns = ::PQnfields(presults);
+			mNumberRows = ::PQntuples(presults);
+			// Store pointer to results
+			mpResults = presults;
+		}
+		break;
+
+	case PGRES_COMMAND_OK:
+		// Command completed, and didn't return anything
+		{
+			mQueryReturnedData = false;
+			// Find the number of changed rows
+			const char *changed = ::PQcmdTuples(presults);
+			if(changed == NULL || changed[0] == '\0')
+			{
+				mChangedRows = 0;
+			}
+			else
+			{
+				mChangedRows = BoxConvert::Convert<int32_t, const char *>(changed);
+			}
+			// Store pointer to results
+			mpResults = presults;
+		}
+		break;
+
+	case PGRES_EMPTY_QUERY:
+		// Empty SQL statements aren't allowed in this interface, consider it a bad SQL statement
+		{
+			::PQclear(presults);
+			THROW_EXCEPTION(DatabaseException, BadSQLStatement)
+		}
+		break;
+
+	case PGRES_NONFATAL_ERROR:
+	case PGRES_FATAL_ERROR:
+		{
+			mrDriver.ReportPostgreSQLError();
+			::PQclear(presults);
+			THROW_EXCEPTION(DatabaseException, ErrorExecutingSQL)
+		}
+		break;
+
+	default:
+		{
+			::PQclear(presults);
+			THROW_EXCEPTION(DatabaseException, UnexpectedLibraryBehaviour)
+		}
+		break;
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::GetNumberChanges()
+//		Purpose: Return number of changes
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+int DbQueryPostgreSQL::GetNumberChanges() const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	if(mQueryReturnedData)
+	{
+		// No rows changed
+		return 0;
+	}
+	
+	return mChangedRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::GetNumberRows()
+//		Purpose: Get number of rows
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+int DbQueryPostgreSQL::GetNumberRows() const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::GetNumberColumns()
+//		Purpose: Get number of columns
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+int DbQueryPostgreSQL::GetNumberColumns() const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mNumberColumns;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::Next()
+//		Purpose: Move cursor to next row
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+bool DbQueryPostgreSQL::Next()
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Check that the caller isn't attempting to advance when the end has already been found
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, AttemptToMoveBeyondQueryEnd)
+	}
+
+	// Advance to next row
+	++mCurrentRow;
+
+	// Got a row?
+	return mCurrentRow < mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::HaveRow()
+//		Purpose: Row available?
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+bool DbQueryPostgreSQL::HaveRow() const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mCurrentRow >= 0 && mCurrentRow < mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::Finish()
+//		Purpose: Finish with this query
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbQueryPostgreSQL::Finish()
+{
+	// Check that this call is appropraite
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Free results
+	::PQclear(mpResults);
+
+	// Reset data
+	mpResults = 0;
+	mQueryReturnedData = true;
+	mChangedRows = -1;
+	mNumberRows = 0;
+	mNumberColumns = 0;
+	mCurrentRow = -1;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::IsFieldNull(int)
+//		Purpose: Is the field null?
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+bool DbQueryPostgreSQL::IsFieldNull(int Column) const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == -1)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Simple check
+	return ::PQgetisnull(mpResults, mCurrentRow, Column) == 1;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::GetFieldInt(int)
+//		Purpose: Get a field's value, as int
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+int32_t DbQueryPostgreSQL::GetFieldInt(int Column) const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == -1)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Get value
+	const char *value = ::PQgetvalue(mpResults, mCurrentRow, Column);
+
+	// Do something vaguely sensible with null values
+	// NOTE: a NULL pointer should never be returned according to the docs, but be paranoid
+	if(value == NULL)
+	{
+		return 0;
+	}
+
+#ifdef PLATFORM_POSTGRESQL_OLD_API
+	// Convert the number
+	if(value[0] == '\0') return 0;
+	return BoxConvert::Convert<int32_t, const char *>(value);
+#else
+	// ------- NEW API -------
+	if(::PQgetisnull(mpResults, mCurrentRow, Column))
+	{
+		// Handle nulls gracefully.
+		return 0;
+	}
+	switch(::PQfformat(mpResults, Column))
+	{
+	case FORMAT_TEXT:
+		if(value[0] == '\0') return 0;
+		return BoxConvert::Convert<int32_t, const char *>(value);
+		break;
+
+	case FORMAT_BINARY:
+		switch(::PQftype(mpResults, Column))
+		{
+		#define CASE_PG_STRING_TYPES \
+		case NAMEOID: \
+		case TEXTOID: \
+		case VARCHAROID: \
+		case BPCHAROID:
+		CASE_PG_STRING_TYPES
+			if(value[0] == '\0') return 0;
+			return BoxConvert::Convert<int32_t, const char *>(value);
+			break;
+
+		// Binary numeric types
+		case BOOLOID:	// Single byte types
+		case CHAROID:
+			return value[0];
+			break;
+
+		case INT2OID:	// 2 byte types
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+			return ntohs(*((int16_t*)value));
+#else
+			return *((int16_t*)value);
+#endif
+			break;
+
+		case INT4OID:	// 4 byte types
+		case OIDOID:
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+			return ntohl(*((int32_t*)value));
+#else
+			return *((int32_t*)value);
+#endif
+			break;
+
+		case INT8OID:	// 8 byte types
+			{
+#ifdef PG_BYTE_SWAPPING_REQUIRED
+				int64_t v = ntoh64(*((int64_t*)value));
+#else
+				int64_t v = *((int64_t*)value);
+#endif
+				if(v > LONG_MAX || v < LONG_MIN)
+				{
+					// Overflow
+					THROW_EXCEPTION(DatabaseException, IntegerOverflow)
+				}
+				// Cast down
+				return (int32_t)v;
+			}
+			break;
+
+		case NUMERICOID:
+			{
+				numeric *num = (numeric*)value;
+				long v = 0;
+				if(::PGTYPESnumeric_to_long(num, &v) != 0)
+				{
+					THROW_EXCEPTION(DatabaseException, PostgreSQLBadConversionFromNumericType)		
+				}
+				return v;
+			}
+			break;
+
+		default:
+			TRACE1("Unhandled pg type is %d\n", ::PQftype(mpResults, Column));
+			THROW_EXCEPTION(DatabaseException, PostgreSQLUnhandledBinaryType)
+			break;
+		}
+		break;
+
+	default:
+		// Other values are not defined
+		THROW_EXCEPTION(DatabaseException, UnexpectedLibraryBehaviour)
+		break;
+	}
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQueryPostgreSQL::GetFieldString(int, std::string &)
+//		Purpose: Get a field's value, as string
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+void DbQueryPostgreSQL::GetFieldString(int Column, std::string &rStringOut) const
+{
+	if(mpResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == -1)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Get value
+	const char *value = ::PQgetvalue(mpResults, mCurrentRow, Column);
+
+	// Do something vaguely sensible with null values (not that this should ever really happen)
+	if(value == NULL)
+	{
+		rStringOut = std::string(); // ie clear();
+		return;
+	}
+
+#ifdef PLATFORM_POSTGRESQL_OLD_API
+	// Copy string into output string
+	rStringOut = value;
+#else
+	// ------- NEW API -------
+	if(::PQgetisnull(mpResults, mCurrentRow, Column))
+	{
+		// Handle nulls gracefully.
+		rStringOut = "";
+		return;
+	}
+	switch(::PQfformat(mpResults, Column))
+	{
+	case FORMAT_TEXT:
+		rStringOut = value;
+		break;
+
+	case FORMAT_BINARY:
+		switch(::PQftype(mpResults, Column))
+		{
+		CASE_PG_STRING_TYPES
+			rStringOut = value;
+			break;
+		default:
+			THROW_EXCEPTION(DatabaseException, PostgreSQLUnhandledBinaryType)
+			break;
+		}
+		break;
+
+	default:
+		// Other values are not defined
+		THROW_EXCEPTION(DatabaseException, UnexpectedLibraryBehaviour)
+		break;
+	}
+#endif
+}
+

Added: box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_postgresql/DbQueryPostgreSQL.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,66 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbQueryPostgreSQL.h
+//		Purpose: Query object for PostgreSQL driver
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBQUERYPOSTGRESQL__H
+#define DBQUERYPOSTGRESQL__H
+
+#include "postgresql/libpq-fe.h"
+
+#include "DatabaseDriver.h"
+
+class DbDriverPostgreSQL;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DbQueryPostgreSQL
+//		Purpose: Query object for PostgreSQL driver
+//		Created: 26/12/04
+//
+// --------------------------------------------------------------------------
+class DbQueryPostgreSQL : public DatabaseDrvQuery
+{
+public:
+	DbQueryPostgreSQL(DbDriverPostgreSQL &rDriver);
+	~DbQueryPostgreSQL();
+private:
+	// no copying
+	DbQueryPostgreSQL(const DbQueryPostgreSQL &);
+	DbQueryPostgreSQL &operator=(const DbQueryPostgreSQL &);
+public:
+
+	virtual void Execute(const char *SQLStatement, int NumberParameters,
+		const Database::FieldType_t *pParameterTypes, const void **pParameters);
+
+	virtual int GetNumberChanges() const;
+	virtual int GetNumberRows() const;
+	virtual int GetNumberColumns() const;
+
+	virtual bool Next();
+	virtual bool HaveRow() const;
+
+	virtual void Finish();
+
+	virtual bool IsFieldNull(int Column) const;
+
+	virtual int32_t GetFieldInt(int Column) const;
+	virtual void GetFieldString(int Column, std::string &rStringOut) const;
+
+private:
+	DbDriverPostgreSQL &mrDriver;
+	PGresult *mpResults;
+	bool mQueryReturnedData;
+	int64_t mChangedRows;
+	int mNumberRows;
+	int mNumberColumns;
+	int mCurrentRow;
+};
+
+#endif // DBQUERYPOSTGRESQL__H
+

Added: box/features/codeforintegration/lib/dbdrv_postgresql/PostgreSQLOidTypes.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_postgresql/PostgreSQLOidTypes.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_postgresql/PostgreSQLOidTypes.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,71 @@
+// Automatically generated file, do not edit.
+//
+// Used because including the catalog/pg_type.h file is a bit complicated
+// because of various assumptions used by packagers on some platforms.
+//
+
+#ifndef POSTGRESQLOIDTYPES__H
+#define POSTGRESQLOIDTYPES__H
+
+#define BOOLOID			16
+#define BYTEAOID		17
+#define CHAROID			18
+#define NAMEOID			19
+#define INT8OID			20
+#define INT2OID			21
+#define INT2VECTOROID	22
+#define INT4OID			23
+#define REGPROCOID		24
+#define TEXTOID			25
+#define OIDOID			26
+#define TIDOID		27
+#define XIDOID 28
+#define CIDOID 29
+#define OIDVECTOROID	30
+#define POINTOID		600
+#define LSEGOID			601
+#define PATHOID			602
+#define BOXOID			603
+#define POLYGONOID		604
+#define LINEOID			628
+#define FLOAT4OID 700
+#define FLOAT8OID 701
+#define ABSTIMEOID		702
+#define RELTIMEOID		703
+#define TINTERVALOID	704
+#define UNKNOWNOID		705
+#define CIRCLEOID		718
+#define CASHOID 790
+#define MACADDROID 829
+#define INETOID 869
+#define CIDROID 650
+#define ACLITEMOID		1033
+#define BPCHAROID		1042
+#define VARCHAROID		1043
+#define DATEOID			1082
+#define TIMEOID			1083
+#define TIMESTAMPOID	1114
+#define TIMESTAMPTZOID	1184
+#define INTERVALOID		1186
+#define TIMETZOID		1266
+#define BITOID	 1560
+#define VARBITOID	  1562
+#define NUMERICOID		1700
+#define REFCURSOROID	1790
+#define REGPROCEDUREOID 2202
+#define REGOPEROID		2203
+#define REGOPERATOROID	2204
+#define REGCLASSOID		2205
+#define REGTYPEOID		2206
+#define RECORDOID		2249
+#define CSTRINGOID		2275
+#define ANYOID			2276
+#define ANYARRAYOID		2277
+#define VOIDOID			2278
+#define TRIGGEROID		2279
+#define LANGUAGE_HANDLEROID		2280
+#define INTERNALOID		2281
+#define OPAQUEOID		2282
+#define ANYELEMENTOID	2283
+
+#endif // POSTGRESQLOIDTYPES__H

Added: box/features/codeforintegration/lib/dbdrv_postgresql/getpostgresqloidtypes.pl
===================================================================
--- box/features/codeforintegration/lib/dbdrv_postgresql/getpostgresqloidtypes.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_postgresql/getpostgresqloidtypes.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+use strict;
+
+my $in = $ARGV[0];
+if($in eq '' || !-f $in)
+{
+	die "No input file, or file not found"
+}
+
+open IN,$in or die "can't open input";
+open OUT,">PostgreSQLOidTypes.h" or die "can't open output";
+
+print OUT <<__E;
+// Automatically generated file, do not edit.
+//
+// Used because including the catalog/pg_type.h file is a bit complicated
+// because of various assumptions used by packagers on some platforms.
+//
+
+#ifndef POSTGRESQLOIDTYPES__H
+#define POSTGRESQLOIDTYPES__H
+
+__E
+
+while(<IN>)
+{
+	print OUT if m/#define.+OID.+?\d+/;
+}
+
+print OUT <<__E;
+
+#endif // POSTGRESQLOIDTYPES__H
+__E
+
+close OUT;
+close IN;
+


Property changes on: box/features/codeforintegration/lib/dbdrv_postgresql/getpostgresqloidtypes.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,218 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverSqlite.cpp
+//		Purpose: Database driver for Sqllite
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <memory>
+
+#include "DbDriverSqlite.h"
+#include "DbQuerySqlite.h"
+#include "autogen_DatabaseException.h"
+#include "DbDriverSqliteV3.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::DbDriverSqlite()
+//		Purpose: Constructor
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DbDriverSqlite::DbDriverSqlite()
+	: mpConnection(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::~DbDriverSqlite()
+//		Purpose: Destructor
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DbDriverSqlite::~DbDriverSqlite()
+{
+	if(mpConnection != 0)
+	{
+		Disconnect();
+	}
+	ASSERT(mpConnection == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::GetDriverName()
+//		Purpose: Name of driver
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+const char *DbDriverSqlite::GetDriverName() const
+{
+	return "sqlite";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::Connect(const std::string &, int)
+//		Purpose: Connect to database, Connection string is filename
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverSqlite::Connect(const std::string &rConnectionString, int Timeout)
+{
+	if(mpConnection != 0)
+	{
+		THROW_EXCEPTION(DatabaseException, AlreadyConnected)
+	}
+
+#ifdef PLATFORM_SQLITE3
+	mpConnection = 0;
+	if(::sqlite3_open(rConnectionString.c_str(), &mpConnection) != SQLITE_OK)
+	{
+		ReportErrorMessage(::sqlite3_errmsg(mpConnection));
+		if(mpConnection != 0)
+		{
+			::sqlite3_close(mpConnection);
+		}
+		THROW_EXCEPTION(DatabaseException, FailedToConnect)
+	}
+#else
+	// mode in sqlite_open is ignored at the moment, and no "future proof" value
+	// seems to be defined in the documentation. So just use 0, and hope for the best.
+	char *errmsg = 0;
+	mpConnection = ::sqlite_open(rConnectionString.c_str(), 0 /* open mode */, &errmsg);
+	if(mpConnection == NULL)
+	{
+		ReportErrorMessage(errmsg);
+		THROW_EXCEPTION(DatabaseException, FailedToConnect)
+	}
+#endif
+	
+	// Set the timeout for waiting for the lock.
+	// This is actually slightly different behaviour than a timeout for connecting to the database,
+	// but is more appropraite to the database itself since there is no real notion of connecting,
+	// and this will give sensible behaviour when the database gets concurrent use.
+	::sqlite_busy_timeout(mpConnection, Timeout);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::Query()
+//		Purpose: Returns a new query object
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DatabaseDrvQuery *DbDriverSqlite::Query()
+{
+	return new DbQuerySqlite(*this);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::Disconnect()
+//		Purpose: Disconnect from the database
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverSqlite::Disconnect()
+{
+	if(mpConnection == 0)
+	{
+		// Nothing to do
+		return;
+	}
+
+	// Close connection to database
+	::sqlite_close(mpConnection);
+	mpConnection = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::QuoteString(const char *, std::string &)
+//		Purpose: Quote a string ready for inclusion into some nice SQL
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbDriverSqlite::QuoteString(const char *pString, std::string &rStringQuotedOut) const
+{
+	ASSERT(pString != 0);
+
+	// Ask SQLite to do this for us, in a not terribly efficient manner.
+	char *quoted = ::sqlite_mprintf("%Q", pString);
+
+	// Check returned value
+	if(quoted == NULL)
+	{
+		throw std::bad_alloc();
+	}
+
+	// Copy into output string
+	try
+	{
+		rStringQuotedOut = quoted;
+	}
+	catch(...)
+	{
+		::sqlite_freemem(quoted);
+		throw;
+	}
+
+	// Free temporary output block
+	::sqlite_freemem(quoted);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::GetLastAutoIncrementValue(const char *, const char *)
+//		Purpose: Get the last inserted value
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+int32_t DbDriverSqlite::GetLastAutoIncrementValue(const char *TableName, const char *ColumnName)
+{
+	return ::sqlite_last_insert_rowid(mpConnection);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::GetGenericTranslations()
+//		Purpose: Get translations for generics in SQL statements
+//		Created: 15/5/04
+//
+// --------------------------------------------------------------------------
+const DatabaseDriver::TranslateMap_t &DbDriverSqlite::GetGenericTranslations()
+{
+	static DatabaseDriver::TranslateMap_t table;
+	const char *from[] = {"AUTO_INCREMENT_INT", "LIMIT2", "CREATE_INDEX_CASE_INSENSTIVE3", "COLUMN_CASE_INSENSITIVE_ORDERING", "CASE_INSENSITIVE_COLUMN1", 0};
+	const char *to[] = {"INTEGER PRIMARY KEY", "LIMIT !1 OFFSET !0", "CREATE INDEX !0 ON !1 (!2)", "COLLATE NOCASE", "!0", 0};
+	DATABASE_DRIVER_FILL_TRANSLATION_TABLE(table, from, to);
+	return table;
+}
+
+

Added: box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqlite.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,73 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverSqlite.h
+//		Purpose: Database driver for Sqllite
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBDRIVERSQLITE__H
+#define DBDRIVERSQLITE__H
+
+#ifdef PLATFORM_SQLITE3
+	#include "sqlite3.h"
+#else
+	#include "sqlite.h"
+#endif
+
+#include "DatabaseDriver.h"
+
+class DbQuerySqlite;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DbDriverSqlite
+//		Purpose: Database driver for Sqllite
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+class DbDriverSqlite : public DatabaseDriver
+{
+	friend class DbQuerySqlite;
+public:
+	DbDriverSqlite();
+	virtual ~DbDriverSqlite();
+private:
+	// no copying
+	DbDriverSqlite(const DbDriverSqlite &);
+	DbDriverSqlite &operator=(const DbDriverSqlite &);
+public:
+
+	virtual const char *GetDriverName() const;
+
+	virtual void Connect(const std::string &rConnectionString, int Timeout);
+	virtual DatabaseDrvQuery *Query();
+	virtual void Disconnect();
+
+	virtual int32_t GetLastAutoIncrementValue(const char *TableName, const char *ColumnName);
+
+	virtual void QuoteString(const char *pString, std::string &rStringQuotedOut) const;
+
+	virtual const TranslateMap_t &GetGenericTranslations();
+
+protected:
+	// For query objects to get the open database file
+#ifdef PLATFORM_SQLITE3
+	sqlite3 *
+#else
+	sqlite *
+#endif
+		GetSqlite() const {return mpConnection;}
+
+private:
+#ifdef PLATFORM_SQLITE3
+	sqlite3 *mpConnection;
+#else
+	sqlite *mpConnection;
+#endif
+};
+
+#endif // DBDRIVERSQLITE__H
+

Added: box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqliteV3.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqliteV3.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqliteV3.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,29 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbDriverSqliteV3.h
+//		Purpose: Compatibility layer for v3 sqlite
+//		Created: 4/11/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBDRIVERSQLITEV3__H
+#define DBDRIVERSQLITEV3__H
+
+// The sqlite driver was originally written for the v2 series of SQLite.
+// Redefine things to use the new names. Incompatible functions are done with
+// ifdefs in the usual way.
+
+#ifdef PLATFORM_SQLITE3
+	#define sqlite_busy_timeout			sqlite3_busy_timeout
+	#define	sqlite_close				sqlite3_close
+	#define sqlite_freemem				sqlite3_free
+	#define sqlite_mprintf				sqlite3_mprintf
+	#define sqlite_last_insert_rowid	sqlite3_last_insert_rowid
+	#define sqlite_get_table			sqlite3_get_table
+	#define sqlite_changes				sqlite3_changes
+	#define sqlite_free_table			sqlite3_free_table
+#endif
+
+#endif // DBDRIVERSQLITEV3__H
+


Property changes on: box/features/codeforintegration/lib/dbdrv_sqlite/DbDriverSqliteV3.h
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.cpp
===================================================================
--- box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,365 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbQuerySqlite.cpp
+//		Purpose: Query object for Sqlite driver
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef PLATFORM_SQLITE3
+	#include "sqlite3.h"
+#else
+	#include "sqlite.h"
+#endif
+
+#include "DbQuerySqlite.h"
+#include "DbDriverSqlite.h"
+#include "autogen_DatabaseException.h"
+#include "Conversion.h"
+#include "DbDriverInsertParameters.h"
+#include "DbDriverSqliteV3.h"
+
+#include "MemLeakFindOn.h"
+
+// How to access a field
+#define FIELD(column) (mppResults[((mCurrentRow+1)*mNumberColumns)+column])
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbDriverSqlite::DbQuerySqlite()
+//		Purpose: Constructor
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DbQuerySqlite::DbQuerySqlite(DbDriverSqlite &rDriver)
+	: mrDriver(rDriver),
+	  mNumberRows(0),
+	  mNumberColumns(0),
+	  mppResults(0),
+	  mCurrentRow(-1),
+	  mNumberChanges(-1)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::~DbQuerySqlite()
+//		Purpose: Destructor
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+DbQuerySqlite::~DbQuerySqlite()
+{
+	if(mppResults != 0)
+	{
+		Finish();
+	}
+	ASSERT(mppResults == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    Execute(const char *, int, const Database::FieldType_t *, const void **)
+//		Purpose: Execute a query
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbQuerySqlite::Execute(const char *SQLStatement, int NumberParameters,
+	const Database::FieldType_t *pParameterTypes, const void **pParameters)
+{
+	if(mppResults != 0)
+	{
+		// Clean up the existing query object
+		Finish();
+	}
+
+	// Run the query, returning all the results at once
+	char **ppresults = 0;
+	int nRows = 0, nColumns = 0;
+	char *errmsg = 0;
+	int returnCode = -1;
+	// If there are parameters, then they need to be inserted into the SQL manually
+	// by the driver -- there is no support in SQLite for this.
+	if(NumberParameters != 0)
+	{
+		// Insert parameters, using the provided utility function
+		std::string sql;
+		DbDriverInsertParameters(SQLStatement, NumberParameters, pParameterTypes, pParameters, mrDriver, sql);
+		// Execute it
+		returnCode = ::sqlite_get_table(mrDriver.GetSqlite(), sql.c_str(), &ppresults, &nRows, &nColumns, &errmsg);
+		if(returnCode != SQLITE_OK)
+		{
+			TRACE1("SQL was '%s'\n", sql.c_str());
+		}
+	}
+	else
+	{
+		// Can just use this string
+		returnCode = ::sqlite_get_table(mrDriver.GetSqlite(), SQLStatement, &ppresults, &nRows, &nColumns, &errmsg);
+		if(returnCode != SQLITE_OK)
+		{
+			TRACE1("SQL was '%s'\n", SQLStatement);
+		}
+	}
+
+	// Check return code
+	if(returnCode != SQLITE_OK)
+	{
+		mrDriver.ReportErrorMessage(errmsg);
+		THROW_EXCEPTION(DatabaseException, BadSQLStatement)
+	}
+	
+	// Store the pointer to the returned data, and the size of the arrays
+	mppResults = ppresults;
+	mNumberRows = nRows;
+	mNumberColumns = nColumns;
+	mCurrentRow = -1;
+	mNumberChanges = ::sqlite_changes(mrDriver.GetSqlite());
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::GetNumberChanges()
+//		Purpose: Return number of changes
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+int DbQuerySqlite::GetNumberChanges() const
+{
+	return mNumberChanges;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::GetNumberRows()
+//		Purpose: Get number of rows
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+int DbQuerySqlite::GetNumberRows() const
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::GetNumberColumns()
+//		Purpose: Get number of columns
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+int DbQuerySqlite::GetNumberColumns() const
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mNumberColumns;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::Next()
+//		Purpose: Move cursor to next row
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+bool DbQuerySqlite::Next()
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Check that the caller isn't attempting to advance when the end has already been found
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, AttemptToMoveBeyondQueryEnd)
+	}
+
+	// Advance to next row
+	++mCurrentRow;
+
+	// Got a row?
+	return mCurrentRow < mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::HaveRow()
+//		Purpose: Row available?
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+bool DbQuerySqlite::HaveRow() const
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	return mCurrentRow >= 0 && mCurrentRow < mNumberRows;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::Finish()
+//		Purpose: Finish with this query
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbQuerySqlite::Finish()
+{
+	// Check that this call is appropraite
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+
+	// Clean up
+	::sqlite_free_table(mppResults);
+
+	// All done
+	mppResults = 0;
+	mNumberRows = 0;
+	mNumberColumns = 0;
+	mCurrentRow = -1;
+	mNumberChanges = -1;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::IsFieldNull(int)
+//		Purpose: Is the field null?
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+bool DbQuerySqlite::IsFieldNull(int Column) const
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == -1)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Simple check
+	return FIELD(Column) == NULL;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::GetFieldInt(int)
+//		Purpose: Get a field's value, as int
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+int32_t DbQuerySqlite::GetFieldInt(int Column) const
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == -1)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Do something vaguely sensible with null values
+	if(FIELD(Column) == NULL)
+	{
+		return 0;
+	}
+
+	// Convert the number
+	return BoxConvert::Convert<int32_t, const char *>(FIELD(Column));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    DbQuerySqlite::GetFieldString(int, std::string &)
+//		Purpose: Get a field's value, as string
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+void DbQuerySqlite::GetFieldString(int Column, std::string &rStringOut) const
+{
+	if(mppResults == 0)
+	{
+		THROW_EXCEPTION(DatabaseException, QueryNotExecuted)
+	}
+	if(mCurrentRow == -1)
+	{
+		THROW_EXCEPTION(DatabaseException, MustCallNextBeforeReadingData)
+	}
+	if(mCurrentRow >= mNumberRows)
+	{
+		THROW_EXCEPTION(DatabaseException, NoRowAvailable)
+	}
+	if(Column < 0 || Column >= mNumberColumns)
+	{
+		THROW_EXCEPTION(DatabaseException, ColumnOutOfRange)
+	}
+
+	// Do something vaguely sensible with null values
+	if(FIELD(Column) == NULL)
+	{
+		rStringOut = std::string(); // ie clear();
+		return;
+	}
+
+	// Copy string into output string
+	rStringOut = FIELD(Column);
+}
+

Added: box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.h
===================================================================
--- box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.h	                        (rev 0)
+++ box/features/codeforintegration/lib/dbdrv_sqlite/DbQuerySqlite.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,63 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    DbQuerySqlite.h
+//		Purpose: Query object for Sqlite driver
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef DBQUERYSQLITE__H
+#define DBQUERYSQLITE__H
+
+#include "DatabaseDriver.h"
+
+class DbDriverSqlite;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    DbQuerySqlite
+//		Purpose: Query object for Sqlite driver
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+class DbQuerySqlite : public DatabaseDrvQuery
+{
+public:
+	DbQuerySqlite(DbDriverSqlite &rDriver);
+	~DbQuerySqlite();
+private:
+	// no copying
+	DbQuerySqlite(const DbQuerySqlite &);
+	DbQuerySqlite &operator=(const DbQuerySqlite &);
+public:
+
+	virtual void Execute(const char *SQLStatement, int NumberParameters,
+		const Database::FieldType_t *pParameterTypes, const void **pParameters);
+
+	virtual int GetNumberChanges() const;
+	virtual int GetNumberRows() const;
+	virtual int GetNumberColumns() const;
+
+	virtual bool Next();
+	virtual bool HaveRow() const;
+
+	virtual void Finish();
+
+	virtual bool IsFieldNull(int Column) const;
+
+	virtual int32_t GetFieldInt(int Column) const;
+	virtual void GetFieldString(int Column, std::string &rStringOut) const;
+
+private:
+	DbDriverSqlite &mrDriver;
+	int mNumberRows;
+	int mNumberColumns;
+	char **mppResults;
+	int mCurrentRow;
+	int mNumberChanges;
+};
+
+#endif // DBQUERYSQLITE__H
+

Added: box/features/codeforintegration/lib/httpserver/HTTPException.txt
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPException.txt	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPException.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,12 @@
+EXCEPTION HTTP 10
+
+Internal							0
+RequestReadFailed					1
+RequestAlreadyBeenRead				2
+BadRequest							3
+UnknownResponseCodeUsed				4
+NoContentTypeSet					5
+POSTContentTooLong					6
+CannotSetRedirectIfReponseHasData	7
+CannotSetNotFoundIfReponseHasData	8
+NotImplemented						9

Added: box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.cpp
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,158 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPQueryDecoder.cpp
+//		Purpose: Utility class to decode HTTP query strings
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+
+#include "HTTPQueryDecoder.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &)
+//		Purpose: Constructor. Pass in the query contents you want to decode
+//				 the query string into.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto)
+	: mrDecodeInto(rDecodeInto),
+	  mInKey(true),
+	  mEscapedState(0)
+{
+	// Insert the terminator for escaped characters
+	mEscaped[2] = '\0';
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPQueryDecoder::~HTTPQueryDecoder()
+//		Purpose: Destructor.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPQueryDecoder::~HTTPQueryDecoder()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPQueryDecoder::Decode(const char *, int)
+//		Purpose: Decode a chunk of query string -- call several times with
+//				 the bits as they are received, and then call Finish()
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPQueryDecoder::DecodeChunk(const char *pQueryString, int QueryStringSize)
+{
+	for(int l = 0; l < QueryStringSize; ++l)
+	{
+		char c = pQueryString[l];
+		
+		// BEFORE unescaping, check to see if we need to flip key / value
+		if(mEscapedState == 0)
+		{
+			if(mInKey && c == '=')
+			{
+				// Set to store characters in the value
+				mInKey = false;
+				continue;
+			}
+			else if(!mInKey && c == '&')
+			{
+				// Need to store the current key/value pair
+				mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue));
+				// Blank the strings
+				mCurrentKey.erase();
+				mCurrentValue.erase();
+			
+				// Set to store characters in the key
+				mInKey = true;
+				continue;
+			}
+		}
+		
+		// Decode an escaped value?
+		if(mEscapedState == 1)
+		{
+			// Waiting for char one of the escaped hex value
+			mEscaped[0] = c;
+			mEscapedState = 2;
+			continue;
+		}
+		else if(mEscapedState == 2)
+		{
+			// Escaped value, decode it
+			mEscaped[1] = c;	// str terminated in constructor
+			mEscapedState = 0;	// stop being in escaped mode
+			long ch = ::strtol(mEscaped, NULL, 16);
+			if(ch <= 0 || ch > 255)
+			{
+				// Bad character, just ignore
+				continue;
+			}
+			
+			// Use this instead
+			c = (char)ch;
+		}		
+		else if(c == '+')
+		{
+			c = ' ';
+		}
+		else if(c == '%')
+		{
+			mEscapedState = 1;
+			continue;
+		}
+
+		// Store decoded value into the appropriate string
+		if(mInKey)
+		{
+			mCurrentKey += c;
+		}
+		else
+		{
+			mCurrentValue += c;
+		}
+	}
+	
+	// Don't do anything here with left over values, DecodeChunk might be called
+	// again. Let Finish() clean up.
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPQueryDecoder::Finish()
+//		Purpose: Finish the decoding. Necessary to get the last item!
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPQueryDecoder::Finish()
+{
+	// Insert any remaining value.
+	if(!mCurrentKey.empty())
+	{
+		mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue));
+		// Blank values, just in case
+		mCurrentKey.erase();
+		mCurrentValue.erase();
+	}
+}
+
+

Added: box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.h
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.h	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPQueryDecoder.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,47 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPQueryDecoder.h
+//		Purpose: Utility class to decode HTTP query strings
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPQUERYDECODER__H
+#define HTTPQUERYDECODER__H
+
+#include "HTTPRequest.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    HTTPQueryDecoder
+//		Purpose: Utility class to decode HTTP query strings
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPQueryDecoder
+{
+public:
+	HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto);
+	~HTTPQueryDecoder();
+private:
+	// no copying
+	HTTPQueryDecoder(const HTTPQueryDecoder &);
+	HTTPQueryDecoder &operator=(const HTTPQueryDecoder &);
+public:
+
+	void DecodeChunk(const char *pQueryString, int QueryStringSize);
+	void Finish();
+
+private:
+	HTTPRequest::Query_t &mrDecodeInto;
+	std::string mCurrentKey;
+	std::string mCurrentValue;
+	bool mInKey;
+	char mEscaped[4];
+	int mEscapedState;
+};
+
+#endif // HTTPQUERYDECODER__H
+

Added: box/features/codeforintegration/lib/httpserver/HTTPRequest.cpp
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPRequest.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPRequest.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,577 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPRequest.cpp
+//		Purpose: Request object for HTTP connections
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "HTTPRequest.h"
+#include "HTTPQueryDecoder.h"
+#include "autogen_HTTPException.h"
+#include "IOStream.h"
+#include "IOStreamGetLine.h"
+
+#include "MemLeakFindOn.h"
+
+#define MAX_CONTENT_SIZE	(128*1024)
+
+#define ENSURE_COOKIE_JAR_ALLOCATED \
+	if(mpCookies == 0) {mpCookies = new CookieJar_t;}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::HTTPRequest()
+//		Purpose: Constructor
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPRequest::HTTPRequest()
+	: mMethod(Method_UNINITIALISED),
+	  mHostPort(80),	// default if not specified
+	  mHTTPVersion(0),
+	  mContentLength(-1),
+	  mpCookies(0),
+	  mClientKeepAliveRequested(false)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::~HTTPRequest()
+//		Purpose: Destructor
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPRequest::~HTTPRequest()
+{
+	// Clean up any cookies
+	if(mpCookies != 0)
+	{
+		delete mpCookies;
+		mpCookies = 0;
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::Read(IOStreamGetLine &, int)
+//		Purpose: Read the request from an IOStreamGetLine (and attached stream)
+//				 Returns false if there was no valid request, probably due to 
+//				 a kept-alive connection closing.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+bool HTTPRequest::Read(IOStreamGetLine &rGetLine, int Timeout)
+{
+	// Check caller's logic
+	if(mMethod != Method_UNINITIALISED)
+	{
+		THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead)
+	}
+
+	// Read the first line, which is of a different format to the rest of the lines
+	std::string requestLine;
+	if(!rGetLine.GetLine(requestLine, false /* no preprocessing */, Timeout))
+	{
+		// Didn't get the request line, probably end of connection which had been kept alive
+		return false;
+	}
+//	TRACE1("Request line: %s\n", requestLine.c_str());
+
+	// Check the method
+	unsigned int p = 0;	// current position in string
+	if(::strncmp(requestLine.c_str(), "GET ", 4) == 0)
+	{
+		p = 3;
+		mMethod = Method_GET;
+	}
+	else if(::strncmp(requestLine.c_str(), "HEAD ", 5) == 0)
+	{
+		p = 4;
+		mMethod = Method_HEAD;
+	}
+	else if(::strncmp(requestLine.c_str(), "POST ", 5) == 0)
+	{
+		p = 4;
+		mMethod = Method_POST;
+	}
+	else
+	{
+		p = requestLine.find(' ');
+		if(p == std::string::npos)
+		{
+			// No terminating space, looks bad
+			p = requestLine.size();
+		}
+		mMethod = Method_UNKNOWN;
+	}
+
+	// Skip spaces to find URI
+	const char *requestLinePtr = requestLine.c_str();
+	while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ')
+	{
+		++p;
+	}
+	
+	// Check there's a URI following...
+	if(requestLinePtr[p] == '\0')
+	{
+		// Didn't get the request line, probably end of connection which had been kept alive
+		return false;
+	}
+	
+	// Read the URI, unescaping any %XX hex codes
+	while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
+	{
+		// End of URI, on to query string?
+		if(requestLinePtr[p] == '?')
+		{
+			// Put the rest into the query string, without escaping anything
+			++p;
+			while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
+			{
+				mQueryString += requestLinePtr[p];
+				++p;
+			}
+			break;
+		}
+		// Needs unescaping?
+		else if(requestLinePtr[p] == '+')
+		{
+			mRequestURI += ' ';
+		}
+		else if(requestLinePtr[p] == '%')
+		{
+			// Be tolerant about this... bad things are silently accepted,
+			// rather than throwing an error.
+			char code[4] = {0,0,0,0};
+			code[0] = requestLinePtr[++p];
+			if(code[0] != '\0')
+			{
+				code[1] = requestLinePtr[++p];
+			}
+			
+			// Convert into a char code
+			long c = ::strtol(code, NULL, 16);
+			
+			// Accept it?
+			if(c > 0 && c <= 255)
+			{
+				mRequestURI += (char)c;
+			}
+		}
+		else
+		{
+			// Simple copy of character
+			mRequestURI += requestLinePtr[p];
+		}
+
+		++p;
+	}
+
+	// End of URL?
+	if(requestLinePtr[p] == '\0')
+	{
+		// Assume HTTP 0.9
+		mHTTPVersion = HTTPVersion_0_9;
+	}
+	else
+	{
+		// Skip any more spaces
+		while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ')
+		{
+			++p;
+		}
+
+		// Check to see if there's the right string next...
+		if(::strncmp(requestLinePtr + p, "HTTP/", 5) == 0)
+		{
+			// Find the version numbers
+			int major, minor;
+			if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2)
+			{
+				THROW_EXCEPTION(HTTPException, BadRequest)		
+			}
+			
+			// Store version
+			mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor;
+		}
+		else
+		{
+			// Not good -- wrong string found
+			THROW_EXCEPTION(HTTPException, BadRequest)		
+		}
+	}
+	
+	TRACE3("HTTPRequest: method=%d, uri=%s, version=%d\n", mMethod, mRequestURI.c_str(), mHTTPVersion);
+	
+	// If HTTP 1.1 or greater, assume keep-alive
+	if(mHTTPVersion >= HTTPVersion_1_1)
+	{
+		mClientKeepAliveRequested = true;
+	}
+	
+	// Decode query string?
+	if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty())
+	{
+		HTTPQueryDecoder decoder(mQuery);
+		decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size());
+		decoder.Finish();
+	}
+	
+	// Now parse the headers
+	ParseHeaders(rGetLine, Timeout);
+	
+	// Parse form data?
+	if(mMethod == Method_POST && mContentLength >= 0)
+	{
+		// Too long? Don't allow people to be nasty by sending lots of data
+		if(mContentLength > MAX_CONTENT_SIZE)
+		{
+			THROW_EXCEPTION(HTTPException, POSTContentTooLong)
+		}
+	
+		// Some data in the request to follow, parsing it bit by bit
+		HTTPQueryDecoder decoder(mQuery);
+		// Don't forget any data left in the GetLine object
+		int fromBuffer = rGetLine.GetSizeOfBufferedData();
+		if(fromBuffer > mContentLength) fromBuffer = mContentLength;
+		if(fromBuffer > 0)
+		{
+			TRACE1("Decoding %d bytes of data from getline buffer\n", fromBuffer);
+			decoder.DecodeChunk((const char *)rGetLine.GetBufferedData(), fromBuffer);
+			// And tell the getline object to ignore the data we just used
+			rGetLine.IgnoreBufferedData(fromBuffer);
+		}
+		// Then read any more data, as required
+		int bytesToGo = mContentLength - fromBuffer;
+		while(bytesToGo > 0)
+		{
+			char buf[4096];
+			int toRead = sizeof(buf);
+			if(toRead > bytesToGo) toRead = bytesToGo;
+			IOStream &rstream(rGetLine.GetUnderlyingStream());
+			int r = rstream.Read(buf, toRead, Timeout);
+			if(r == 0)
+			{
+				// Timeout, just error
+				THROW_EXCEPTION(HTTPException, RequestReadFailed)
+			}
+			decoder.DecodeChunk(buf, r);
+			bytesToGo -= r;
+		}
+		// Finish off
+		decoder.Finish();
+	}
+	
+	return true;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::ParseHeaders(IOStreamGetLine &, int)
+//		Purpose: Private. Parse the headers of the request
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
+{
+	std::string header;
+	bool haveHeader = false;
+	while(true)
+	{
+		if(rGetLine.IsEOF())
+		{
+			// Header terminates unexpectedly
+			THROW_EXCEPTION(HTTPException, BadRequest)		
+		}
+
+		std::string currentLine;	
+		if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
+		{
+			// Timeout
+			THROW_EXCEPTION(HTTPException, RequestReadFailed)
+		}
+		
+		// Is this a continuation of the previous line?
+		bool processHeader = haveHeader;
+		if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
+		{
+			// A continuation, don't process anything yet
+			processHeader = false;
+		}
+		//TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
+		
+		// Parse the header -- this will actually process the header
+		// from the previous run around the loop.
+		if(processHeader)
+		{
+			// Find where the : is in the line
+			const char *h = header.c_str();
+			int p = 0;
+			while(h[p] != '\0' && h[p] != ':')
+			{
+				++p;
+			}
+			// Skip white space
+			int dataStart = p + 1;
+			while(h[dataStart] == ' ' || h[dataStart] == '\t')
+			{
+				++dataStart;
+			}
+		
+			if(p == sizeof("Content-Length")-1
+				&& ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0)
+			{
+				// Decode number
+				long len = ::strtol(h + dataStart, NULL, 10);	// returns zero in error case, this is OK
+				if(len < 0) len = 0;
+				// Store
+				mContentLength = len;
+			}
+			else if(p == sizeof("Content-Type")-1
+				&& ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0)
+			{
+				// Store rest of string as content type
+				mContentType = h + dataStart;
+			}
+			else if(p == sizeof("Host")-1
+				&& ::strncasecmp(h, "Host", sizeof("Host")-1) == 0)
+			{
+				// Store host header
+				mHostName = h + dataStart;
+				
+				// Is there a port number to split off?
+				std::string::size_type colon = mHostName.find_first_of(':');
+				if(colon != std::string::npos)
+				{
+					// There's a port in the string... attempt to turn it into an int
+					mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10);
+					
+					// Truncate the string to just the hostname
+					mHostName = mHostName.substr(0, colon);
+					
+					TRACE2("Host: header, hostname = '%s', host port = %d\n", mHostName.c_str(), mHostPort);
+				}
+			}
+			else if(p == sizeof("Cookie")-1
+				&& ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0)
+			{
+				// Parse cookies
+				ParseCookies(header, dataStart);
+			}
+			else if(p == sizeof("Connection")-1
+				&& ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0)
+			{
+				// Connection header, what is required?
+				const char *v = h + dataStart;
+				if(::strcasecmp(v, "close") == 0)
+				{
+					mClientKeepAliveRequested = false;
+				}
+				else if(::strcasecmp(v, "keep-alive") == 0)
+				{
+					mClientKeepAliveRequested = true;
+				}
+				// else don't understand, just assume default for protocol version
+			}
+			// else ignore it
+			
+			// Unset have header flag, as it's now been processed
+			haveHeader = false;
+		}
+
+		// Store the chunk of header the for next time round
+		if(haveHeader)
+		{
+			header += currentLine;
+		}
+		else
+		{
+			header = currentLine;
+			haveHeader = true;
+		}
+
+		// End of headers?
+		if(currentLine.empty())
+		{
+			// All done!
+			break;
+		}		
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::ParseCookies(const std::string &, int)
+//		Purpose: Parse the cookie header
+//		Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
+{
+	const char *data = rHeader.c_str() + DataStarts;
+	const char *pos = data;
+	const char *itemStart = pos;
+	std::string name;
+	
+	enum
+	{
+		s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME
+	} state = s_NAME;
+
+	do	
+	{
+		switch(state)
+		{
+		case s_NAME:
+			{
+				if(*pos == '=')
+				{
+					// Found the name. Store
+					name.assign(itemStart, pos - itemStart);
+					// Looking at values now
+					state = s_VALUE;
+					if((*(pos + 1)) == '"')
+					{
+						// Actually it's a quoted value, skip over that
+						++pos;
+						state = s_VALUE_QUOTED;
+					}
+					// Record starting point for this item
+					itemStart = pos + 1;
+				}
+			}
+			break;
+		
+		case s_VALUE:
+			{
+				if(*pos == ';' || *pos == ',' || *pos == '\0')
+				{
+					// Name ends
+					ENSURE_COOKIE_JAR_ALLOCATED
+					std::string value(itemStart, pos - itemStart);
+					(*mpCookies)[name] = value;
+					// And move to the waiting stage
+					state = s_FIND_NEXT_NAME;
+				}
+			}
+			break;
+		
+		case s_VALUE_QUOTED:
+			{
+				if(*pos == '"')
+				{
+					// That'll do nicely, save it
+					ENSURE_COOKIE_JAR_ALLOCATED
+					std::string value(itemStart, pos - itemStart);
+					(*mpCookies)[name] = value;
+					// And move to the waiting stage
+					state = s_FIND_NEXT_NAME;
+				}
+			}
+			break;
+		
+		case s_FIND_NEXT_NAME:
+			{
+				// Skip over terminators and white space to get to the next name
+				if(*pos != ';' && *pos != ',' && *pos != ' ' && *pos != '\t')
+				{
+					// Name starts here
+					itemStart = pos;
+					state = s_NAME;
+				}
+			}
+			break;
+		
+		default:
+			// Ooops
+			THROW_EXCEPTION(HTTPException, Internal)
+			break;
+		}
+	}
+	while(*(pos++) != 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::GetCookie(const char *, std::string &) const
+//		Purpose: Fetch a cookie's value. If cookie not present, returns false
+//				 and string is unaltered.
+//		Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) const
+{
+	// Got any cookies?
+	if(mpCookies == 0)
+	{
+		return false;
+	}
+	
+	// See if it's there
+	CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
+	if(v != mpCookies->end())
+	{
+		// Return the value
+		rValueOut = v->second;
+		return true;
+	}
+	
+	return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPRequest::GetCookie(const char *)
+//		Purpose: Return a string for the given cookie, or the null string if the
+//				 cookie has not been recieved.
+//		Created: 22/8/04
+//
+// --------------------------------------------------------------------------
+const std::string &HTTPRequest::GetCookie(const char *CookieName) const
+{
+	static const std::string noCookie;
+
+	// Got any cookies?
+	if(mpCookies == 0)
+	{
+		return noCookie;
+	}
+	
+	// See if it's there
+	CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
+	if(v != mpCookies->end())
+	{
+		// Return the value
+		return v->second;
+	}
+	
+	return noCookie;
+}
+
+
+

Added: box/features/codeforintegration/lib/httpserver/HTTPRequest.h
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPRequest.h	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPRequest.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,114 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPRequest.h
+//		Purpose: Request object for HTTP connections
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPREQUEST__H
+#define HTTPREQUEST__H
+
+#include <string>
+#include <map>
+
+class IOStream;
+class IOStreamGetLine;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    HTTPRequest
+//		Purpose: Request object for HTTP connections
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPRequest
+{
+public:
+	HTTPRequest();
+	~HTTPRequest();
+private:
+	// no copying
+	HTTPRequest(const HTTPRequest &);
+	HTTPRequest &operator=(const HTTPRequest &);
+public:
+
+	typedef std::multimap<std::string, std::string> Query_t;
+	typedef std::pair<std::string, std::string> QueryEn_t;
+
+	enum
+	{
+		Method_UNINITIALISED = -1,
+		Method_UNKNOWN = 0,
+		Method_GET = 1,
+		Method_HEAD = 2,
+		Method_POST = 3
+	};
+	
+	enum
+	{
+		HTTPVersion__MajorMultiplier = 1000,
+		HTTPVersion_0_9 = 9,
+		HTTPVersion_1_0 = 1000,
+		HTTPVersion_1_1 = 1001
+	};
+
+	bool Read(IOStreamGetLine &rGetLine, int Timeout);
+
+	typedef std::map<std::string, std::string> CookieJar_t;
+	
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    HTTPResponse::Get*()
+	//		Purpose: Various Get accessors
+	//		Created: 26/3/04
+	//
+	// --------------------------------------------------------------------------
+	int GetMethod() const {return mMethod;}
+	const std::string &GetRequestURI() const {return mRequestURI;}
+	const std::string &GetHostName() const {return mHostName;}	// note: request does splitting of Host: header
+	const int GetHostPort() const {return mHostPort;}  // into host name and port number
+	const std::string &GetQueryString() const {return mQueryString;}
+	int GetHTTPVersion() const {return mHTTPVersion;}
+	const Query_t &GetQuery() const {return mQuery;}
+	int GetContentLength() const {return mContentLength;}
+	const std::string &GetContentType() const {return mContentType;}
+	const CookieJar_t *GetCookies() const {return mpCookies;} // WARNING: May return NULL
+	bool GetCookie(const char *CookieName, std::string &rValueOut) const;
+	const std::string &GetCookie(const char *CookieName) const;
+
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    HTTPRequest::GetClientKeepAliveRequested()
+	//		Purpose: Returns true if the client requested that the connection
+	//				 should be kept open for further requests.
+	//		Created: 22/12/04
+	//
+	// --------------------------------------------------------------------------
+	bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;}
+
+private:
+	void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
+	void ParseCookies(const std::string &rHeader, int DataStarts);
+
+private:
+	int mMethod;
+	std::string mRequestURI;
+	std::string mHostName;
+	int mHostPort;
+	std::string mQueryString;
+	int mHTTPVersion;
+	Query_t mQuery;
+	int mContentLength;
+	std::string mContentType;
+	CookieJar_t *mpCookies;
+	bool mClientKeepAliveRequested;
+};
+
+#endif // HTTPREQUEST__H
+

Added: box/features/codeforintegration/lib/httpserver/HTTPResponse.cpp
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPResponse.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPResponse.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,415 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPResponse.cpp
+//		Purpose: Response object for HTTP connections
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "HTTPResponse.h"
+#include "autogen_HTTPException.h"
+
+#include "MemLeakFindOn.h"
+
+// Static variables
+std::string HTTPResponse::msDefaultURIPrefix;
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::HTTPResponse()
+//		Purpose: Constructor
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPResponse::HTTPResponse()
+	: mResponseCode(HTTPResponse::Code_NoContent),
+	  mResponseIsDynamicContent(true),
+	  mKeepAlive(false)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::~HTTPResponse()
+//		Purpose: Destructor
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPResponse::~HTTPResponse()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::ResponseCodeToString(int)
+//		Purpose: Return string equivalent of the response code, suitable for Status: headers
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const char *HTTPResponse::ResponseCodeToString(int ResponseCode)
+{
+	switch(ResponseCode)
+	{
+	case Code_OK: return "200 OK"; break;
+	case Code_NoContent: return "204 No Content"; break;
+	case Code_MovedPermanently: return "301 Moved Permanently"; break;
+	case Code_Found: return "302 Found"; break;
+	case Code_NotModified: return "304 Not Modified"; break;
+	case Code_TemporaryRedirect: return "307 Temporary Redirect"; break;
+	case Code_Unauthorized: return "401 Unauthorized"; break;
+	case Code_Forbidden: return "403 Forbidden"; break;
+	case Code_NotFound: return "404 Not Found"; break;
+	case Code_InternalServerError: return "500 Internal Server Error"; break;
+	case Code_NotImplemented: return "501 Not Implemented"; break;
+	default:
+		{
+			THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed)
+		}
+	}
+	return "500 Internal Server Error";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::SetResponseCode(int)
+//		Purpose: Set the response code to be returned
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetResponseCode(int Code)
+{
+	mResponseCode = Code;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::SetContentType(const char *)
+//		Purpose: Set content type
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetContentType(const char *ContentType)
+{
+	mContentType = ContentType;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::Send(IOStream &, bool)
+//		Purpose: Build the response, and send via the stream. Optionally omitting
+//				 the content.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::Send(IOStream &rStream, bool OmitContent)
+{
+	if(mContentType.empty())
+	{
+		THROW_EXCEPTION(HTTPException, NoContentTypeSet)
+	}
+
+	// Build and send header
+	{
+		std::string header("HTTP/1.1 ");
+		header += ResponseCodeToString(mResponseCode);
+		header += "\r\nContent-Type: ";
+		header += mContentType;
+		header += "\r\nContent-Length: ";
+		{
+			char len[32];
+			::sprintf(len, "%d", OmitContent?(0):(GetSize()));
+			header += len;
+		}
+		// Extra headers...
+		for(std::vector<std::string>::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
+		{
+			header += "\r\n";
+			header += *i;
+		}
+		// NOTE: a line ending must be included here in all cases
+		// Control whether the response is cached
+		if(mResponseIsDynamicContent)
+		{
+			// dynamic is private and can't be cached
+			header += "\r\nCache-Control: no-cache, private";
+		}
+		else
+		{
+			// static is allowed to be cached for a day
+			header += "\r\nCache-Control: max-age=86400";
+		}
+		if(mKeepAlive)
+		{
+			header += "\r\nConnection: keep-alive\r\n\r\n";
+		}
+		else
+		{
+			header += "\r\nConnection: close\r\n\r\n";
+		}
+		// NOTE: header ends with blank line in all cases
+		
+		// Write to stream
+		rStream.Write(header.c_str(), header.size());
+	}
+	
+	// Send content
+	if(!OmitContent)
+	{
+		rStream.Write(GetBuffer(), GetSize());
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::AddHeader(const char *)
+//		Purpose: Add header, given entire line
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *EntireHeaderLine)
+{
+	mExtraHeaders.push_back(std::string(EntireHeaderLine));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::AddHeader(const std::string &)
+//		Purpose: Add header, given entire line
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
+{
+	mExtraHeaders.push_back(rEntireHeaderLine);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::AddHeader(const char *, const char *)
+//		Purpose: Add header, given header name and it's value
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *Header, const char *Value)
+{
+	std::string h(Header);
+	h += ": ";
+	h += Value;
+	mExtraHeaders.push_back(h);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::AddHeader(const char *, const std::string &)
+//		Purpose: Add header, given header name and it's value
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *Header, const std::string &rValue)
+{
+	std::string h(Header);
+	h += ": ";
+	h += rValue;
+	mExtraHeaders.push_back(h);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::AddHeader(const std::string &, const std::string &)
+//		Purpose: Add header, given header name and it's value
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue)
+{
+	mExtraHeaders.push_back(rHeader + ": " + rValue);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::SetCookie(const char *, const char *, const char *, int)
+//		Purpose: Sets a cookie, using name, value, path and expiry time.
+//		Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt)
+{
+	if(ExpiresAt != 0)
+	{
+		THROW_EXCEPTION(HTTPException, NotImplemented)
+	}
+
+	// Appears you shouldn't use quotes when you generate set-cookie headers.
+	// Oh well. It was fun finding that out.
+/*	std::string h("Set-Cookie: ");
+	h += Name;
+	h += "=\"";
+	h += Value;
+	h += "\"; Version=\"1\"; Path=\"";
+	h += Path;
+	h += "\"";
+*/
+	std::string h("Set-Cookie: ");
+	h += Name;
+	h += "=";
+	h += Value;
+	h += "; Version=1; Path=";
+	h += Path;
+
+	mExtraHeaders.push_back(h);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::SetAsRedirect(const char *, bool)
+//		Purpose: Sets the response objects to be a redirect to another page.
+//				 If IsLocalURL == true, the default prefix will be added.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI)
+{
+	if(mResponseCode != HTTPResponse::Code_NoContent
+		|| !mContentType.empty()
+		|| GetSize() != 0)
+	{
+		THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData)
+	}
+
+	// Set response code
+	mResponseCode = Code_Found;
+
+	// Set location to redirect to
+	std::string header("Location: ");
+	if(IsLocalURI) header += msDefaultURIPrefix;
+	header += RedirectTo;
+	mExtraHeaders.push_back(header);
+	
+	// Set up some default content
+	mContentType = "text/html";
+	#define REDIRECT_HTML_1 "<html><head><title>Redirection</title></head>\n<body><p><a href=\""
+	#define REDIRECT_HTML_2 "\">Redirect to content</a></p></body></html>\n"
+	Write(REDIRECT_HTML_1, sizeof(REDIRECT_HTML_1) - 1);
+	if(IsLocalURI) Write(msDefaultURIPrefix.c_str(), msDefaultURIPrefix.size());
+	Write(RedirectTo, ::strlen(RedirectTo));
+	Write(REDIRECT_HTML_2, sizeof(REDIRECT_HTML_2) - 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::SetAsNotFound(const char *)
+//		Purpose: Set the response object to be a standard page not found 404 response.
+//		Created: 7/4/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetAsNotFound(const char *URI)
+{
+	if(mResponseCode != HTTPResponse::Code_NoContent
+		|| mExtraHeaders.size() != 0
+		|| !mContentType.empty()
+		|| GetSize() != 0)
+	{
+		THROW_EXCEPTION(HTTPException, CannotSetNotFoundIfReponseHasData)
+	}
+
+	// Set response code
+	mResponseCode = Code_NotFound;
+
+	// Set data
+	mContentType = "text/html";
+	#define NOT_FOUND_HTML_1 "<html><head><title>404 Not Found</title></head>\n<body><h1>404 Not Found</h1>\n<p>The URI <i>"
+	#define NOT_FOUND_HTML_2 "</i> was not found on this server.</p></body></html>\n"
+	Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1);
+	WriteStringDefang(std::string(URI));
+	Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPResponse::WriteStringDefang(const char *, unsigned int)
+//		Purpose: Writes a string 'defanged', ie has HTML special characters escaped
+//				 so that people can't output arbitary HTML by playing with
+//				 URLs and form parameters, and it's safe to write strings into
+//				 HTML element attribute values.
+//		Created: 9/4/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen)
+{
+	while(StringLen > 0)
+	{
+		unsigned int toWrite = 0;
+		while(toWrite < StringLen 
+			&& String[toWrite] != '<' 
+			&& String[toWrite] != '>'
+			&& String[toWrite] != '&'
+			&& String[toWrite] != '"')
+		{
+			++toWrite;
+		}
+		if(toWrite > 0)
+		{
+			Write(String, toWrite);
+			StringLen -= toWrite;
+			String += toWrite;
+		}
+		
+		// Is it a bad character next?
+		while(StringLen > 0)
+		{
+			bool notSpecial = false;
+			switch(*String)
+			{
+				case '<': Write("&lt;", 4); break;
+				case '>': Write("&gt;", 4); break;
+				case '&': Write("&amp;", 5); break;
+				case '"': Write("&quot;", 6); break;
+				default:
+					// Stop this loop
+					notSpecial = true;
+					break;
+			}
+			if(notSpecial) break;
+			++String;
+			--StringLen;
+		}
+	}
+}
+
+

Added: box/features/codeforintegration/lib/httpserver/HTTPResponse.h
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPResponse.h	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPResponse.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,115 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPResponse.h
+//		Purpose: Response object for HTTP connections
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPRESPONSE__H
+#define HTTPRESPONSE__H
+
+#include <string>
+#include <vector>
+
+#include "CollectInBufferStream.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    HTTPResponse
+//		Purpose: Response object for HTTP connections
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPResponse : public CollectInBufferStream
+{
+public:
+	HTTPResponse();
+	~HTTPResponse();
+private:
+	// no copying
+	HTTPResponse(const HTTPResponse &);
+	HTTPResponse &operator=(const HTTPResponse &);
+public:
+
+	void SetResponseCode(int Code);
+	void SetContentType(const char *ContentType);
+
+	void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true);
+	void SetAsNotFound(const char *URI);
+
+	void Send(IOStream &rStream, bool OmitContent = false);
+
+	void AddHeader(const char *EntireHeaderLine);
+	void AddHeader(const std::string &rEntireHeaderLine);
+	void AddHeader(const char *Header, const char *Value);
+	void AddHeader(const char *Header, const std::string &rValue);
+	void AddHeader(const std::string &rHeader, const std::string &rValue);
+
+	// Set dynamic content flag, default is content is dynamic
+	void SetResponseIsDynamicContent(bool IsDynamic) {mResponseIsDynamicContent = IsDynamic;}
+	// Set keep alive control, default is to mark as to be closed
+	void SetKeepAlive(bool KeepAlive) {mKeepAlive = KeepAlive;}
+
+	void SetCookie(const char *Name, const char *Value, const char *Path = "/", int ExpiresAt = 0);
+
+	enum
+	{
+		Code_OK = 200,
+		Code_NoContent = 204,
+		Code_MovedPermanently = 301,
+		Code_Found = 302,	// redirection
+		Code_NotModified = 304,
+		Code_TemporaryRedirect = 307,
+		Code_Unauthorized = 401,
+		Code_Forbidden = 403,
+		Code_NotFound = 404,
+		Code_InternalServerError = 500,
+		Code_NotImplemented = 501
+	};
+
+	static const char *ResponseCodeToString(int ResponseCode);
+	
+	void WriteStringDefang(const char *String, unsigned int StringLen);
+	void WriteStringDefang(const std::string &rString) {WriteStringDefang(rString.c_str(), rString.size());}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    HTTPResponse::WriteString(const std::string &)
+	//		Purpose: Write a string to the response (simple sugar function)
+	//		Created: 9/4/04
+	//
+	// --------------------------------------------------------------------------
+	void WriteString(const std::string &rString)
+	{
+		Write(rString.c_str(), rString.size());
+	}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    HTTPResponse::SetDefaultURIPrefix(const std::string &)
+	//		Purpose: Set default prefix used to local redirections
+	//		Created: 26/3/04
+	//
+	// --------------------------------------------------------------------------
+	static void SetDefaultURIPrefix(const std::string &rPrefix)
+	{
+		msDefaultURIPrefix = rPrefix;
+	}
+
+private:
+	int mResponseCode;
+	bool mResponseIsDynamicContent;
+	bool mKeepAlive;
+	std::string mContentType;
+	std::vector<std::string> mExtraHeaders;
+	
+	static std::string msDefaultURIPrefix;
+};
+
+#endif // HTTPRESPONSE__H
+

Added: box/features/codeforintegration/lib/httpserver/HTTPServer.cpp
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPServer.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPServer.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,249 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPServer.cpp
+//		Purpose: HTTP server class
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "HTTPServer.h"
+#include "HTTPRequest.h"
+#include "HTTPResponse.h"
+#include "IOStreamGetLine.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::HTTPServer()
+//		Purpose: Constructor
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPServer::HTTPServer()
+	: mTimeout(20000)	// default timeout leaves a little while for clients to get the second request in.
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::~HTTPServer()
+//		Purpose: Destructor
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPServer::~HTTPServer()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::DaemonName()
+//		Purpose: As interface, generic name for daemon
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const char *HTTPServer::DaemonName() const
+{
+	return "generic-httpserver";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::GetConfigVerify()
+//		Purpose: As interface -- return most basic config so it's only necessary to
+//				 provide this if you want to add extra directives.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const ConfigurationVerify *HTTPServer::GetConfigVerify() const
+{
+	static ConfigurationVerifyKey verifyserverkeys[] = 
+	{
+		HTTPSERVER_VERIFY_SERVER_KEYS(0)	// no default addresses
+	};
+
+	static ConfigurationVerify verifyserver[] = 
+	{
+		{
+			"Server",
+			0,
+			verifyserverkeys,
+			ConfigTest_Exists | ConfigTest_LastEntry,
+			0
+		}
+	};
+	
+	static ConfigurationVerifyKey verifyrootkeys[] = 
+	{
+		HTTPSERVER_VERIFY_ROOT_KEYS
+	};
+
+	static ConfigurationVerify verify =
+	{
+		"root",
+		verifyserver,
+		verifyrootkeys,
+		ConfigTest_Exists | ConfigTest_LastEntry,
+		0
+	};
+
+	return &verify;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::Run()
+//		Purpose: As interface.
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::Run()
+{
+	// Do some configuration stuff
+	const Configuration &conf(GetConfiguration());
+	HTTPResponse::SetDefaultURIPrefix(conf.GetKeyValue("AddressPrefix"));
+
+	// Let the base class do the work
+	ServerStream<SocketStream, 80>::Run();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::Connection(SocketStream &) 
+//		Purpose: As interface, handle connection
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::Connection(SocketStream &rStream)
+{
+	// Create a get line object to use
+	IOStreamGetLine getLine(rStream);
+
+	// Notify dervived claases
+	HTTPConnectionOpening();
+
+	bool handleRequests = true;
+	while(handleRequests)
+	{
+		// Parse the request
+		HTTPRequest request;
+		if(!request.Read(getLine, mTimeout))
+		{
+			// Didn't get request, connection probably closed.
+			break;
+		}
+	
+		// Generate a response
+		HTTPResponse response;
+		try
+		{
+			Handle(request, response);
+		}
+		catch(BoxException &e)
+		{
+			char exceptionCode[64];
+			::sprintf(exceptionCode, "(%d/%d)", e.GetType(), e.GetSubType());
+			SendInternalErrorResponse(exceptionCode, rStream);
+			return;
+		}
+		catch(...)
+		{
+			SendInternalErrorResponse("unknown", rStream);
+			return;
+		}
+		
+		// Keep alive?
+		if(request.GetClientKeepAliveRequested())
+		{
+			// Mark the response to the client as supporting keepalive
+			response.SetKeepAlive(true);
+		}
+		else
+		{
+			// Stop now
+			handleRequests = false;
+		}
+	
+		// Send the response (omit any content if this is a HEAD method request)
+		response.Send(rStream, request.GetMethod() == HTTPRequest::Method_HEAD);
+	}
+
+	// Notify dervived claases
+	HTTPConnectionClosing();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::SendInternalErrorResponse(const char *, SocketStream &)
+//		Purpose: Sends an error response to the remote side
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::SendInternalErrorResponse(const char *Error, SocketStream &rStream)
+{
+	#define ERROR_HTML_1 "<html><head><title>Internal Server Error</title></head>\n" \
+			"<h1>Internal Server Error</h1>\n" \
+			"<p>An error, type "
+	#define ERROR_HTML_2 " occured when processing the request.</p>" \
+			"<p>Please try again later.</p>" \
+			"</body>\n</html>\n"
+
+	// Generate the error page
+	HTTPResponse response;
+	response.SetResponseCode(HTTPResponse::Code_InternalServerError);
+	response.SetContentType("text/html");
+	response.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1);
+	response.Write(Error, ::strlen(Error));
+	response.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1);
+
+	// Send the error response
+	response.Send(rStream);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::HTTPConnectionOpening()
+//		Purpose: Override to get notifications of connections opening
+//		Created: 22/12/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::HTTPConnectionOpening()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    HTTPServer::HTTPConnectionClosing()
+//		Purpose: Override to get notifications of connections closing
+//		Created: 22/12/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::HTTPConnectionClosing()
+{
+}
+
+

Added: box/features/codeforintegration/lib/httpserver/HTTPServer.h
===================================================================
--- box/features/codeforintegration/lib/httpserver/HTTPServer.h	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/HTTPServer.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,78 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    HTTPServer.h
+//		Purpose: HTTP server class
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPSERVER__H
+#define HTTPSERVER__H
+
+#include "ServerStream.h"
+#include "SocketStream.h"
+
+class HTTPRequest;
+class HTTPResponse;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    HTTPServer
+//		Purpose: HTTP server
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPServer : public ServerStream<SocketStream, 80>
+{
+public:
+	HTTPServer();
+	~HTTPServer();
+private:
+	// no copying
+	HTTPServer(const HTTPServer &);
+	HTTPServer &operator=(const HTTPServer &);
+public:
+
+	int GetTimeout() const {return mTimeout;}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    HTTPServer::Handle(const HTTPRequest &, HTTPResponse &)
+	//		Purpose: Response to a request, filling in the response object for sending
+	//				 at some point in the future.
+	//		Created: 26/3/04
+	//
+	// --------------------------------------------------------------------------
+	virtual void Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse) = 0;
+	
+	// For notifications to derived classes
+	virtual void HTTPConnectionOpening();
+	virtual void HTTPConnectionClosing();
+
+private:
+	const char *DaemonName() const;
+	const ConfigurationVerify *GetConfigVerify() const;
+	void Run();
+	void Connection(SocketStream &rStream);
+	void SendInternalErrorResponse(const char *Error, SocketStream &rStream);
+
+private:
+	int mTimeout;	// Timeout for read operations
+};
+
+// Root level
+#define HTTPSERVER_VERIFY_ROOT_KEYS \
+	{"AddressPrefix", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+
+// AddressPrefix is, for example, http://localhost:1080 -- ie the beginning of the URI
+// This is used for handling redirections.
+
+// Server level
+#define HTTPSERVER_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \
+	SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES)
+
+#endif // HTTPSERVER__H
+

Added: box/features/codeforintegration/lib/httpserver/Makefile.extra
===================================================================
--- box/features/codeforintegration/lib/httpserver/Makefile.extra	                        (rev 0)
+++ box/features/codeforintegration/lib/httpserver/Makefile.extra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,7 @@
+
+MAKEEXCEPTION = ../../lib/common/makeexception.pl
+
+# AUTOGEN SEEDING
+autogen_HTTPException.h autogen_HTTPException.cpp:	$(MAKEEXCEPTION) HTTPException.txt
+	perl $(MAKEEXCEPTION) HTTPException.txt
+

Added: box/features/codeforintegration/lib/perl/CppDataClass.pm
===================================================================
--- box/features/codeforintegration/lib/perl/CppDataClass.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/perl/CppDataClass.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,208 @@
+package CppDataClass;
+use strict;
+use CppVariable;
+
+use constant PUBLIC => 1;
+use constant PRIVATE => 2;
+use constant PROTECTED => 3;
+
+# create a new class, from a class name and list of items
+sub new
+{
+	my ($type, $name, $derived_from, @items) = @_;
+
+	my $self = {};
+	$$self{'classname'} = $name;
+	$$self{'derived_from'} = $derived_from;
+	$$self{'items'} = [@items];
+	$$self{'declarations'} = [];
+	$$self{'options'} = {};
+	
+	bless $self, $type;
+	$self
+}
+
+# Set an option
+# 	ReadOnly, boolean, should Set() accessors be generated?
+#	PublicVars, boolean, should the vars be public?
+sub set_option
+{
+	my ($self, $option, $value) = @_;
+	
+	my @allowed = qw/ReadOnly PublicVars/;
+	my $found = 0;
+	for(@allowed) {$found = 1 if $_ eq $option;}
+	die "Option $option is not known" unless $found;
+	
+	${$$self{'options'}}{$option} = $value;
+}
+
+	
+# adds functions -- use all syntax including last ; if appropraite
+sub add_declarations
+{
+	my ($self, $decl_type, @declarations) = @_;
+
+	for(@declarations)
+	{
+		push @{$$self{'declarations'}}, [$decl_type, $_]
+	}
+}
+
+# return the class name
+sub get_class_name
+{
+	my ($self) = @_;
+	return $$self{'classname'}
+}
+
+# return a reference to an array of all the data members
+sub get_data_members_ref
+{
+	my ($self) = @_;
+	return $$self{'items'}
+}
+
+# generates the text for the .h file
+sub generate_h
+{
+	my ($self) = @_;
+
+	my $classname = $$self{'classname'};
+	my $derivation = ($$self{'derived_from'} ne '')?(" : public ".$$self{'derived_from'}):'';
+
+	my $h = <<__E;
+class ${classname}$derivation
+{
+public:
+	${classname}();
+	${classname}(const ${classname} &rToCopy);
+	${classname} &operator=(const ${classname} &rToCopy);
+	~${classname}();
+__E
+
+	# add any extra public functions
+	$h .= $self->generate_declarations(PUBLIC);
+	
+	# add data accessor functions
+	for my $v (@{$$self{'items'}})
+	{
+		$h .= "\t".$v->fn_return_type().' Get'.$v->name().'() const {return m'.$v->name().";}\n"
+	}
+	# if not read only, generate set functions
+	if(!${$$self{'options'}}{'ReadOnly'})
+	{
+		for my $v (@{$$self{'items'}})
+		{
+			$h .= "\tvoid Set".$v->name().'('.$v->fn_arg_type().' '.$v->name.') {m'.$v->name().' = '.$v->name().";}\n";
+		}
+	}
+	
+	# add any other private or protected functions
+	my $priv = $self->generate_declarations(PRIVATE);
+	if($priv ne '')
+	{
+		$h .= "private:\n".$priv;
+	}
+	my $prot = $self->generate_declarations(PROTECTED);
+	if($prot ne '')
+	{
+		$h .= "protected:\n".$prot;
+	}
+	
+	# output the variables
+	$h .= "private:\n";
+	for my $v (@{$$self{'items'}})
+	{
+		$h .= "\t".$v->type().' m'.$v->name().";\n"
+	}	
+	
+	# final essential bit of syntax
+	$h .= "};\n";
+	
+	# return the results
+	$h
+}
+
+# generates the text for the .cpp file
+sub generate_cpp
+{
+	my ($self) = @_;
+
+	my $classname = $$self{'classname'};
+	
+	# Default constructor
+	my $cpp = "${classname}::${classname}()";
+	my @default_init;
+	my $first = 1;
+	for my $v (@{$$self{'items'}})
+	{
+		# does this value need an default initialiser?
+		if($v->needs_default_initaliser())
+		{
+			# add them to the text out
+			$cpp .= ($first)?"\n    : ":",\n      ";
+			$cpp .= 'm'.$v->name().'('.$v->default_value_allow_type_default().')';
+			$first = 0;
+		}
+	}
+	$cpp .= "\n{\n}\n";
+	
+	my $base = $$self{'derived_from'};
+	
+	# Copy constructor
+	$cpp .= "${classname}::${classname}(const ${classname} &rToCopy)";
+	$first = "\n\t: ";
+	if($base ne '')
+	{
+		$cpp .= "\n\t: $base(rToCopy)";
+		$first = '';
+	}
+	for my $v (@{$$self{'items'}})
+	{
+		$cpp .= ($first ne '')?$first:",\n\t  ";
+		$cpp .= 'm'.$v->name().'(rToCopy.m'.$v->name().')';
+		$first = '';
+	}
+	$cpp .= "\n{\n}\n";
+	
+	# operator=
+	$cpp .= "${classname} &${classname}::operator=(const ${classname} &rToCopy)\n{\n";
+	if($base ne '')
+	{
+		$cpp .= "\t${base}::operator=(rToCopy);\n"
+	}
+	for my $v (@{$$self{'items'}})
+	{
+		$cpp .= "\tm".$v->name().' = rToCopy.m'.$v->name().";\n";
+	}
+	$cpp .= "\treturn *this;\n}\n";
+	
+	# destructor
+	$cpp .= "${classname}::~${classname}()\n{\n}\n";
+
+	# all done	
+	$cpp
+}
+
+# internal. Generates a string containing function declarations
+sub generate_declarations
+{
+	my ($self, $decl_type) = @_;
+	
+	my $r;
+	
+	for my $f (@{$$self{'declarations'}})
+	{
+		if($$f[0] == $decl_type)
+		{
+			$r .= "\t";
+			$r .= $$f[1];
+			$r .= "\n";
+		}
+	}
+	
+	$r
+}
+
+1;

Added: box/features/codeforintegration/lib/perl/CppVariable.pm
===================================================================
--- box/features/codeforintegration/lib/perl/CppVariable.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/perl/CppVariable.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,240 @@
+package CppVariable;
+use strict;
+use base 'Exporter';
+use vars qw/@EXPORT @_not_composite/;
+@EXPORT = qw(cppvar);
+
+use constant TYPE => 0;
+use constant NAME => 1;
+use constant DEFAULT_VALUE => 2;
+
+@_not_composite = qw/bool float double/;
+
+# Neat constructor function, exported
+sub cppvar
+{
+	CppVariable->new(@_);
+}
+
+sub new
+{
+	my ($type, @params) = @_;
+	
+	my $self;
+	
+	# construct either from single param of space separated items, or a parameter list
+	if($#params == 0)
+	{
+		if(ref($params[0]) eq 'CppVariable')
+		{
+			$self = [@{$params[0]}]
+		}
+		else
+		{
+			$self = [split /\s+/,$params[0]]
+		}
+	}
+	else
+	{
+		$self = [@params]
+	}
+	
+	# check length
+	if($#$self < NAME || $#$self > DEFAULT_VALUE)
+	{
+		die "Invalid CppVariable constructor arguments"
+	}
+	
+	bless $self, $type;
+	$self
+}
+
+# Take a list of CppVariables and/or string representations, and
+# return a list of CppVariables -- useful for intepreting lists
+# as arguements to functions.
+sub var_list
+{
+	return map {
+		if(ref($_) eq 'CppVariable')
+		{
+			$_
+		}
+		else
+		{
+			cppvar($_)
+		}
+	} @_;
+}
+
+sub name
+{
+	my ($self) = @_;
+	
+	return $$self[NAME];
+}
+
+sub type
+{
+	my ($self) = @_;
+	
+	return $$self[TYPE];
+}
+
+sub fn_arg_type
+{
+	my ($self) = @_;
+	
+	if($self->is_composite_type())
+	{
+		return 'const '.$$self[TYPE].'&'
+	}
+	else
+	{
+		return $$self[TYPE]
+	}
+}
+
+sub fn_return_type
+{
+	my ($self) = @_;
+	
+	# same logic as argument types
+	return $self->fn_arg_type;
+}
+
+sub has_default
+{
+	my ($self) = @_;
+	
+	return ($$self[DEFAULT_VALUE] ne '')
+}
+
+# returns true if this needs an initialiser in a class constructor
+# -- depends on whether it's composite, and whether it has a 
+# default value
+sub needs_default_initaliser
+{
+	my ($self) = @_;
+
+	# non-composite types definately need default values
+	return 1 if !$self->is_composite_type();
+
+	# has it got a default value?
+	return 1 if $$self[DEFAULT_VALUE] ne '';
+
+	# otherwise it can do without
+	return 0;
+}
+
+sub default_value
+{
+	my ($self) = @_;
+
+	die "CppVariable ".$$self[TYPE]." ".$$self[NAME]." does not have a default value set, and needs one.\n"
+		if $$self[DEFAULT_VALUE] eq '';
+	
+	return $$self[DEFAULT_VALUE]
+}
+
+sub default_value_allow_type_default
+{
+	my ($self) = @_;
+	
+	if($$self[DEFAULT_VALUE] eq '')
+	{
+		# no default value specified, return default for type
+		return _default_value_for_type($$self[TYPE]);
+	}
+
+	return $$self[DEFAULT_VALUE]
+}
+
+sub is_composite_type
+{
+	my ($self) = @_;
+	
+	return _is_composite_type($$self[TYPE]);
+}
+
+sub _is_composite_type
+{
+	my ($type) = @_;
+
+	# is it a natural type?
+	return 0 if $type =~ m/\Au?int\d+_t\Z/;
+	for(@_not_composite)
+	{
+		return 0 if $type eq $_;
+	}
+	
+	return 1;
+}
+
+sub is_int_type
+{
+	my ($self) = @_;
+	return $$self[TYPE] =~ m/\Aint(|(\d*)_t)\Z/
+}
+
+sub is_string_type
+{
+	my ($self) = @_;
+	return 1 if $$self[TYPE] eq 'std::string';
+	return 1 if $$self[TYPE] =~ m/\Aconst\s+char\s+\*\Z/;
+	return 0
+}
+
+# returns a suitable default value for a given type
+sub _default_value_for_type
+{
+	my ($type) = @_;
+	
+	if($type =~ m/int/)
+	{
+		return '0';
+	}
+	elsif($type eq 'bool')
+	{
+		return 'false';
+	}
+	elsif($type =~ m/string/i)
+	{
+		return '""';
+	}
+
+	die "Don't know about default value for type $type in _default_value_for_type()"
+}
+
+# Return a statement which converts this variable to the specified type.
+sub convert_to
+{
+	my ($self, $to_type) = @_;
+
+	# check for the easy case first
+	return $$self[NAME] if $to_type eq $$self[TYPE];
+
+	# work out the conversion function call
+	my $conv_from_type = _is_composite_type($$self[TYPE])?"const ".$$self[TYPE]." &":$$self[TYPE];
+	return 'BoxConvert::Convert<'.$to_type.", $conv_from_type>(".$$self[NAME].")"
+}
+
+# Use this function when you have a variable, an another variable, and you
+# want to convert that other variable into a type suitable for assigning
+# to this variable.
+sub convert_from
+{
+	my ($self, @a) = @_;
+	
+	my $from = cppvar(@a);
+	my ($from_type, $from_value) = ($from->type(),$from->name());
+
+	# check for the easy case first
+	return $from_value if $from_type eq $$self[TYPE];
+
+	# work out the conversion function call
+	my $conv_from_type = _is_composite_type($from_type)?"const $from_type &":$from_type;
+	return 'BoxConvert::Convert<'.$$self[TYPE].", $conv_from_type>($from_value)"
+}
+
+1;
+

Added: box/features/codeforintegration/lib/smtpclient/Makefile.extra
===================================================================
--- box/features/codeforintegration/lib/smtpclient/Makefile.extra	                        (rev 0)
+++ box/features/codeforintegration/lib/smtpclient/Makefile.extra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,6 @@
+
+MAKEEXCEPTION = ../../lib/common/makeexception.pl
+
+# AUTOGEN SEEDING
+autogen_SMTPClientException.h autogen_SMTPClientException.cpp:	$(MAKEEXCEPTION) SMTPClientException.txt
+	perl $(MAKEEXCEPTION) SMTPClientException.txt

Added: box/features/codeforintegration/lib/smtpclient/SMTPClient.cpp
===================================================================
--- box/features/codeforintegration/lib/smtpclient/SMTPClient.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/smtpclient/SMTPClient.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,521 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    SMTPClient.cpp
+//		Purpose: Send emails using SMTP
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <limits.h>
+
+#include "SMTPClient.h"
+#include "SocketStream.h"
+#include "Socket.h"
+#include "IOStreamGetLine.h"
+#include "autogen_SMTPClientException.h"
+#include "MemBlockStream.h"
+
+#include "MemLeakFindOn.h"
+
+// Well known port number.
+#define SMTP_PORT	25
+
+// Reply code
+#define SMTPREPLY_POSITIVE_PRELIMIARY(x)	((x) >= 100 && (x) < 200)
+#define SMTPREPLY_POSITIVE_COMPLETION(x)	((x) >= 200 && (x) < 300)
+#define SMTPREPLY_POSITIVE_INTERMEDIATE(x)	((x) >= 300 && (x) < 400)
+#define SMTPREPLY_NEGATIVE_TRANSIENT(x)		((x) >= 400 && (x) < 500)
+#define SMTPREPLY_NEGATIVE_PERMANENT(x)		((x) >= 500 && (x) < 600)
+
+// Internal data
+class SMTPClient_Internals
+{
+public:
+	SMTPClient_Internals() : mGetLine(mSocket) {}
+	~SMTPClient_Internals() {}
+	
+	SocketStream mSocket;
+	IOStreamGetLine mGetLine;
+};
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SMTPClient(const char *, const char *, int)
+//		Purpose: Constructor, taking server name, this host name, and timeout
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+SMTPClient::SMTPClient(const char *Server, const char *ThisHostName, int Timeout)
+	: mServer(Server),
+	  mThisHostName(ThisHostName),
+	  mTimeout(Timeout),
+	  mpImpl(0),
+	  mpClaimedBySender(0)
+{
+	ASSERT(!mServer.empty());
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SMTPClient(const std::string &, const std::string &, int)
+//		Purpose: Constructor, taking server name, this host name, and timeout
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+SMTPClient::SMTPClient(const std::string &rServer, const std::string &rThisHostName, int Timeout)
+	: mServer(rServer),
+	  mThisHostName(rThisHostName),
+	  mTimeout(Timeout),
+	  mpImpl(0),
+	  mpClaimedBySender(0)
+{
+	ASSERT(!mServer.empty());
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::~SMTPClient()
+//		Purpose: Destructor
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+SMTPClient::~SMTPClient()
+{
+	if(mpClaimedBySender != 0)
+	{
+		TRACE1("Destroying SMTPClient when currently in use by SendEmail object 0x%x\n", mpClaimedBySender);
+	}
+	if(mpImpl != 0)
+	{
+		Disconnect();
+	}
+	ASSERT(mpImpl == 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::Connect()
+//		Purpose: Connect to server. Does nothing if already connected.
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::Connect()
+{
+	if(mpImpl != 0)
+	{
+		return;
+	}
+
+	// Open socket
+	try
+	{
+		// Create new object
+		mpImpl = new SMTPClient_Internals;
+		
+		// Open connection
+		mpImpl->mSocket.Open(Socket::TypeINET, mServer.c_str(), SMTP_PORT);
+		
+		// Make sure we get a header
+		int openingStatus = ReceiveStatusCode();
+		if(!SMTPREPLY_POSITIVE_COMPLETION(openingStatus))
+		{
+			THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+		}
+		
+		// Say hello!
+		std::string hello("HELO ");
+		hello += mThisHostName;
+		hello += "\r\n";
+		int result = SendCommand(hello.c_str(), hello.size());
+		if(!SMTPREPLY_POSITIVE_COMPLETION(result))
+		{
+			THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+		}
+	}
+	catch(...)
+	{
+		if(mpImpl != 0)
+		{
+			delete mpImpl;
+		}
+		throw;
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::Disconnect()
+//		Purpose: Disconnect from server. Does nothing if not connected.
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::Disconnect()
+{
+	// Don't disconnect again
+	if(mpImpl == 0)
+	{
+		return;
+	}
+	
+	// Absorb errors, to avoid compounding any problems
+	try
+	{
+		// Send quit command
+		SendCommand("QUIT\r\n", sizeof("QUIT\r\n")-1);
+
+		// Close socket
+		mpImpl->mSocket.Close();
+	}
+	catch(...)
+	{
+		// Ignore
+	}
+	
+	delete mpImpl;
+	mpImpl = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SendEmail::SendEmail(SMTPClient &, const char *)
+//		Purpose: Constructor, taking SMTPClient object and sender email address
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+SMTPClient::SendEmail::SendEmail(SMTPClient &rClient, const char *SenderAddress)
+	: mrClient(rClient),
+	  mSenderAddress(SenderAddress),
+	  mClaimedClient(false)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SendEmail::SendEmail(SMTPClient &, const std::string &)
+//		Purpose: Constructor, taking SMTPClient object and sender email address
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+SMTPClient::SendEmail::SendEmail(SMTPClient &rClient, const std::string &rSenderAddress)
+	: mrClient(rClient),
+	  mSenderAddress(rSenderAddress),
+	  mClaimedClient(false)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SendEmail::SendEmail(SMTPClient &, const char *)
+//		Purpose: Destructor
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+SMTPClient::SendEmail::~SendEmail()
+{
+	if(mClaimedClient)
+	{
+		mrClient.ReleaseClient(this);
+	}
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    SMTPClient::SendEmail::To(const char *)
+//		Purpose: Set a recipient address (can call multiple times)
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::SendEmail::To(const char *ToAddress)
+{
+	EnsureStarted();
+	
+	// Send the command
+	std::string mailTo("RCPT TO:<");
+	mailTo += ToAddress;
+	mailTo += ">\r\n";
+	int result = mrClient.SendCommand(mailTo.c_str(), mailTo.size());
+	if(!SMTPREPLY_POSITIVE_COMPLETION(result))
+	{
+		THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    SMTPClient::SendEmail::To(const std::string &)
+//		Purpose: Set a recipient address (can call multiple times)
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::SendEmail::To(const std::string &rToAddress)
+{
+	EnsureStarted();
+	
+	// Send the command
+	std::string mailTo("RCPT TO:<");
+	mailTo += rToAddress;
+	mailTo += ">\r\n";
+	int result = mrClient.SendCommand(mailTo.c_str(), mailTo.size());
+	if(!SMTPREPLY_POSITIVE_COMPLETION(result))
+	{
+		THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    SMTPClient::SendEmail::Message(IOStream &, bool)
+//		Purpose: Send the actual message
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::SendEmail::Message(IOStream &rMessage, bool ProtectEmailContents)
+{
+	// Claimed?
+	if(!mClaimedClient)
+	{
+		// Means no recpients have been specified
+		THROW_EXCEPTION(SMTPClientException, NoRecipientsSet)		
+	}
+
+	// Send the message data command
+	int result = mrClient.SendCommand("DATA\r\n", sizeof("DATA\r\n")-1);
+	if(!SMTPREPLY_POSITIVE_INTERMEDIATE(result))
+	{
+		THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+	}
+	
+	// Send actual message data
+	if(ProtectEmailContents)
+	{
+		// Need to check for .'s at the beginning of the line.
+		IOStreamGetLine g(rMessage);
+		std::string line;
+		while(!g.IsEOF() && g.GetLine(line))
+		{
+			if(line[0] == '.')
+			{
+				// Send an additional . (see RFC on transparency)
+				mrClient.mpImpl->mSocket.Write(".", 1);
+			}
+			
+			// Send line
+			mrClient.mpImpl->mSocket.Write(line.c_str(), line.size());
+
+			// Terminate line properly
+			mrClient.mpImpl->mSocket.Write("\r\n", 2);
+		}
+	}
+	else
+	{
+		// Caller claims data is OK to send as it is, so just copy it
+		rMessage.CopyStreamTo(mrClient.mpImpl->mSocket);
+	}
+	
+	// Terminate message and check it went OK
+	result = mrClient.SendCommand("\r\n.\r\n", sizeof("\r\n.\r\n")-1);
+	if(!SMTPREPLY_POSITIVE_COMPLETION(result))
+	{
+		THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+	}
+	
+	// Unclaim
+	mrClient.ReleaseClient(this);
+	mClaimedClient = false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SendEmail::EnsureStarted()
+//		Purpose: Private. Ensure that the client is connected and the process
+//				 has started properly.
+//		Created: 29/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::SendEmail::EnsureStarted()
+{
+	// Claim it
+	if(!mClaimedClient)
+	{
+		mrClient.ClaimClient(this);
+		mClaimedClient = true;
+
+		// Ensure it's connected
+		mrClient.Connect();
+		
+		// Send the from command
+		std::string mailFrom("MAIL FROM:<");
+		mailFrom += mSenderAddress;
+		mailFrom += ">\r\n";
+		int result = mrClient.SendCommand(mailFrom.c_str(), mailFrom.size());
+		if(!SMTPREPLY_POSITIVE_COMPLETION(result))
+		{
+			THROW_EXCEPTION(SMTPClientException, ServerReportedError)
+		}
+	}	
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SendCommand(const char *, int)
+//		Purpose: Private. Send a command to the SMTP server, returning the
+//				 SMTP status code.
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+int SMTPClient::SendCommand(const char *Command, int CommandLength)
+{
+	ASSERT(mpImpl != 0);
+	
+	//TRACE1("C: %s", Command);
+	
+	mpImpl->mSocket.Write(Command, CommandLength);
+
+	return ReceiveStatusCode();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::ReceiveStatusCode()
+//		Purpose: Receives a status code, or exceptions if an timeout occured
+//				 or badly formed line receieved.
+//		Created: 29/10/04
+//
+// --------------------------------------------------------------------------
+int SMTPClient::ReceiveStatusCode()
+{
+	ASSERT(mpImpl != 0);
+	
+	// Get a line from readline
+	std::string line;
+	bool finished = false;
+	int statusCode = 0;
+	while(!finished)
+	{
+		// Get a line
+		if(!mpImpl->mGetLine.GetLine(line, false, mTimeout))
+		{
+			// Timeout!
+			THROW_EXCEPTION(SMTPClientException, Timeout)
+		}
+		
+		// The first bit should be a number
+		const char *lineptr = line.c_str();
+		//TRACE1("S: %s\n", lineptr);
+		char *endptr = 0;
+		long code = ::strtol(lineptr, &endptr, 10);
+		if(code == LONG_MIN || code == LONG_MAX || endptr == lineptr || endptr == 0)
+		{
+			// Bad format of line
+			THROW_EXCEPTION(SMTPClientException, UnexpectedServerResponse)
+		}
+		// Code looks OK then
+		statusCode = code;
+		
+		// Continuation?
+		if(*endptr != '-')
+		{
+			finished = true;
+		}
+	}
+	
+	//TRACE1("status=%d\n", statusCode);
+	return statusCode;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::ClaimClient(SendEmail *)
+//		Purpose: Private. Claim the SMTPClient as in use
+//		Created: 29/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::ClaimClient(SMTPClient::SendEmail *pSender)
+{
+	ASSERT(mpClaimedBySender != pSender);
+
+	// Already in use?
+	if(mpClaimedBySender != 0)
+	{
+		THROW_EXCEPTION(SMTPClientException, ClientAlreadyInUse)
+	}
+	
+	// Record
+	mpClaimedBySender = pSender;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::ReleaseClient(SMTPClient::SendEmail *)
+//		Purpose: Private. Release the SMTPClient from use
+//		Created: 29/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::ReleaseClient(SMTPClient::SendEmail *pSender)
+{
+	if(mpClaimedBySender != pSender)
+	{
+		THROW_EXCEPTION(SMTPClientException, InternalClaimError)
+	}
+	
+	// Unset
+	mpClaimedBySender = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    SMTPClient::SendMessage(const std::string &, const std::string &, const std::string &)
+//		Purpose: Quick interface for sending an email to one recipient, and
+//				 always protecting the email sent again the SMTP transparency
+//				 feature.
+//		Created: 29/10/04
+//
+// --------------------------------------------------------------------------
+void SMTPClient::SendMessage(const std::string &rFrom, const std::string &rTo, const std::string &rMessage)
+{
+	SMTPClient::SendEmail send(*this, rFrom);
+	send.To(rTo);
+	MemBlockStream messageStream(rMessage.c_str(), rMessage.size());
+	send.Message(messageStream);
+}
+

Added: box/features/codeforintegration/lib/smtpclient/SMTPClient.h
===================================================================
--- box/features/codeforintegration/lib/smtpclient/SMTPClient.h	                        (rev 0)
+++ box/features/codeforintegration/lib/smtpclient/SMTPClient.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,81 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    SMTPClient.h
+//		Purpose: Send emails using SMTP
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef SMTPCLIENT__H
+#define SMTPCLIENT__H
+
+#include <string>
+class IOStream;
+class SocketStream;
+
+class SMTPClient_Internals;
+
+class SMTPClient
+{
+public:
+	SMTPClient(const char *Server, const char *ThisHostName, int Timeout = 5000);
+	SMTPClient(const std::string &rServer, const std::string &rThisHostName, int Timeout = 5000);
+	~SMTPClient();
+private:
+	// no copying
+	SMTPClient(const SMTPClient &);
+	SMTPClient &operator=(const SMTPClient &);
+public:
+
+	void Connect();
+	void Disconnect();
+	bool IsConnected() const {return mpImpl != 0;}
+	bool InUse() const {return mpClaimedBySender != 0;}
+
+	// Quick interface
+	void SendMessage(const std::string &rFrom, const std::string &rTo, const std::string &rMessage);
+	
+	// More complete interface
+	class SendEmail
+	{
+	public:
+		SendEmail(SMTPClient &rClient, const char *SenderAddress);
+		SendEmail(SMTPClient &rClient, const std::string &rSenderAddress);
+		~SendEmail();
+	private:
+		SendEmail(const SendEmail &);
+		SendEmail &operator=(const SendEmail &);
+	public:
+	
+		void To(const char *ToAddress);
+		void To(const std::string &rToAddress);
+		
+		void Message(IOStream &rMessage, bool ProtectEmailContents = true);	// set to false if the email is guarenetted not to have special lines in it
+	
+	private:
+		void EnsureStarted();
+	
+	private:
+		SMTPClient &mrClient;
+		std::string mSenderAddress;
+		bool mClaimedClient;
+	};
+	friend class SendEmail;
+	
+private:
+	int SendCommand(const char *Command, int CommandLength);
+	int ReceiveStatusCode();
+	void ClaimClient(SendEmail *pSender);
+	void ReleaseClient(SendEmail *pSender);
+
+private:
+	std::string mServer;
+	std::string mThisHostName;
+	int mTimeout;
+	SMTPClient_Internals *mpImpl;
+	SendEmail *mpClaimedBySender;
+};
+
+#endif // SMTPCLIENT__H
+

Added: box/features/codeforintegration/lib/smtpclient/SMTPClientException.txt
===================================================================
--- box/features/codeforintegration/lib/smtpclient/SMTPClientException.txt	                        (rev 0)
+++ box/features/codeforintegration/lib/smtpclient/SMTPClientException.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,9 @@
+EXCEPTION SMTPClient 16
+
+Internal						0
+Timeout							1
+UnexpectedServerResponse		2
+ServerReportedError				3	Wrong reply code class received from server
+ClientAlreadyInUse				4	Tried to send another email while one is already in process
+InternalClaimError				5	Claim released by wrong sender
+NoRecipientsSet					6	At least one recipient must be set when sending an email

Added: box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.cpp
===================================================================
--- box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,184 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    ValidateEmailAddress.cpp
+//		Purpose: 
+//		Created: 7/1/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef PLATFORM_DARWIN
+	// Required to T_MX etc on Darwin
+	#define BIND_8_COMPAT
+#endif
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+
+#include "ValidateEmailAddress.h"
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    ValidateEmailAddress(const std::string &, std::string &, bool)
+//		Purpose: Returns true if the email address is likely to be valid.
+//				 Optionally attempts to look the domain name up in DNS.
+//		Created: 7/1/05
+//
+// --------------------------------------------------------------------------
+bool ValidateEmailAddress(const std::string &rEmailAddress, std::string &rCanonicalAddressOut, bool CheckDomainWithDNS)
+{
+	bool valid = true;
+	
+	// Scan the string, copying into the canonical address out one
+	const char *e = rEmailAddress.c_str();
+	bool inDomain = false;
+	int dotsInDomain = 0;
+	bool foundNonDotInDomain = false;
+	bool startingDomainElement = false;
+	unsigned int domainStarts = 0;
+	std::string canonical;
+	while(*e != '\0')
+	{
+		char c = *e;
+		// Only look at non-whitespace characters
+		if(!::isspace(c))
+		{
+			if(c == '@')
+			{
+				if(inDomain)
+				{
+					// Only have one @ sign
+					valid = false;
+				}
+				else
+				{
+					inDomain = true;
+					startingDomainElement = true;
+					// Any left hand side?
+					if(canonical.size() == 0)
+					{
+						valid = false;
+					}
+					// Record where the domain started
+					domainStarts = canonical.size() + 1;
+				}
+			}
+			// Domain specific checks
+			else if(inDomain)
+			{
+				// Domains must go to lower case
+				c = ::tolower(c);
+
+				// Check for dots
+				if(c == '.')
+				{
+					++dotsInDomain;
+					
+					// Check that the last char wasn't a . as well
+					if(canonical.size() >= 1 && canonical[canonical.size() - 1] == '.')
+					{
+						valid = false;
+					}
+				}
+				else
+				{
+					foundNonDotInDomain = true;
+				}
+				
+				// Check valid characters
+				if(!
+					(
+						(c == '.')
+						|| (c >= 'a' && c <= 'z')
+						|| (c >= '0' && c <= '9')
+					)
+				)
+				{
+					if(c == '-')
+					{
+						// - is not allowed as first element of domain
+						if(startingDomainElement)
+						{
+							valid = false;
+						}
+					}
+					else
+					{
+						valid = false;
+					}
+				}
+
+				// Unflag as start of domain element
+				startingDomainElement = false;
+
+				if(c == '.')
+				{
+					// Mark as starting a domain element
+					startingDomainElement = true;
+				}
+			}
+			
+			
+			// Append char to string.
+			canonical += c;
+		}
+
+		// Next char
+		++e;
+	}
+
+	// Remove trailing dot from canonical address?
+	if(inDomain)
+	{
+		if(canonical[canonical.size() - 1] == '.')
+		{
+			// has a final dot. Remove it from the address
+			canonical.resize(canonical.size() - 1);
+			// One less dot found, though
+			--dotsInDomain;
+		}
+	}
+
+	// Some more checks
+	if(!inDomain || (dotsInDomain == 0) || !foundNonDotInDomain)
+	{
+		valid = false;
+	}
+
+	// If the domain looks valid, check it in DNS
+	if(valid && CheckDomainWithDNS)
+	{
+		unsigned char answer[1024];
+		const char *domain = canonical.c_str() + domainStarts;
+		if(::res_query(domain, C_IN, T_MX, answer, sizeof(answer)) < 0
+			&& h_errno == HOST_NOT_FOUND)
+		{
+			// MX record not found, try the A record instead
+			if(::res_query(domain, C_IN, T_A, answer, sizeof(answer)) < 0
+				&& h_errno == HOST_NOT_FOUND)
+			{
+				valid = false;
+			}
+		}
+		else
+		{
+			// got a reply or there was a lookup failure, so assume the domain is valid
+		}
+	}
+
+	// Copy the canonical address out, and return the validity flag
+	rCanonicalAddressOut = canonical;
+	return valid;
+}
+
+
+

Added: box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.h
===================================================================
--- box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.h	                        (rev 0)
+++ box/features/codeforintegration/lib/smtpclient/ValidateEmailAddress.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,18 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    ValidateEmailAddress.h
+//		Purpose: Validate an email address.
+//		Created: 7/1/05
+//
+// --------------------------------------------------------------------------
+
+#ifndef VALIDATEEMAILADDRESS__H
+#define VALIDATEEMAILADDRESS__H
+
+#include <string>
+
+bool ValidateEmailAddress(const std::string &rEmailAddress, std::string &rCanonicalAddressOut, bool CheckDomainWithDNS = true);
+
+#endif // VALIDATEEMAILADDRESS__H
+

Added: box/features/codeforintegration/lib/webappframework/Makefile.extra
===================================================================
--- box/features/codeforintegration/lib/webappframework/Makefile.extra	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/Makefile.extra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,8 @@
+
+MAKEEXCEPTION = ../../lib/common/makeexception.pl
+
+# AUTOGEN SEEDING
+autogen_WebAppFrameworkException.h autogen_WebAppFrameworkException.cpp:	$(MAKEEXCEPTION) WebAppFrameworkException.txt
+	perl $(MAKEEXCEPTION) WebAppFrameworkException.txt
+
+

Added: box/features/codeforintegration/lib/webappframework/StarterTemplate.txt
===================================================================
--- box/features/codeforintegration/lib/webappframework/StarterTemplate.txt	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/StarterTemplate.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,448 @@
+*** file Makefile.extra
+
+# AUTOGEN SEEDING
+Makefile.webapp:	APP_LONG_NAME.pl
+	perl ../../lib/webappframework/WebApplication.pl APP_LONG_NAME.pl make
+
+# include-makefile: Makefile.webapp
+
+*** if database
+# AUTOGEN SEEDING
+autogen_db/APP_LONG_NAME_schema.h:	APP_LONG_NAME.schema
+	../../lib/database/makedbmake.pl .
+
+# include-makefile: Makefile.db
+*** endif
+
+*** file APP_LONG_NAME.pl
+use WebAppFramework::Unit::PageTemplate;
+use WebAppFramework::Unit::TableContainer;
+use WebAppFramework::Unit::Variable;
+use WebAppFramework::Unit::Form;
+*** if database
+use WebAppFramework::Unit::Database::Table;
+*** endif
+
+$webapp->set_webapp_name('APP_LONG_NAME', 'APP_SHORT_NAME', 'APP_URL_BASE');
+
+*** if database
+# Add the database parameters to the config file
+$webapp->add_extra_config_directive('string', 'DatabaseDriver');
+$webapp->add_extra_config_directive('string', 'DatabaseConnection');
+
+*** endif
+# Add any more languages here
+$webapp->add_language('en');
+$webapp->set_default_langage('en');
+
+# set up global parameters for all pages (example)
+# $webapp->add_global_parameters('std::string Username');
+
+# define each page, and the additional parameters it has
+$webapp->add_page('Main', 'main');
+
+# set defaults for Units
+WebAppFramework::Unit::TableContainer->set_defaults('Template' => 'APP_LONG_NAMEMain');
+WebAppFramework::Unit::PageTemplate->set_defaults('Template' => 'APP_LONG_NAMEMain');
+WebAppFramework::Unit::Form->set_defaults('Template' => 'APP_LONG_NAMEMain', 'FragmentsName' => 'Form');
+WebAppFramework::Unit::FormTableContainer->set_defaults('Template' => 'APP_LONG_NAMEMain', 'FragmentsName' => 'Form');
+*** if database
+WebAppFramework::Unit::Database::Table->set_defaults('Template' => 'APP_LONG_NAMEMain', 'FragmentsName' => 'DatabaseTable');
+*** endif
+
+# Subroutine to set up the basics of the page
+sub setup_page
+{
+	my $page = WebAppFramework::Unit::PageTemplate->new();
+	
+	# Add other units to the page unit as required
+
+	$page
+}
+
+1;
+
+*** file APP_LONG_NAME.h
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    APP_LONG_NAME.h
+//		Purpose: Web application object
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+
+#ifndef APP_LONG_NAME__H
+#define APP_LONG_NAME__H
+
+#include "WebApplicationObject.h"
+*** if database
+#include "DatabaseConnection.h"
+*** endif
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    APP_LONG_NAME
+//		Purpose: Web application
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+class APP_LONG_NAME : public WebApplicationObject
+{
+public:
+	APP_LONG_NAME();
+	~APP_LONG_NAME();
+
+*** if database
+	void ChildStart(const Configuration &rConfiguration);
+	void ChildFinish();
+
+	DatabaseConnection &GetDatabaseConnection() {return mDatabase;}
+
+private:
+	DatabaseConnection mDatabase;
+*** endif
+};
+
+#endif // APP_LONG_NAME__H
+
+*** file APP_LONG_NAME.cpp
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    APP_LONG_NAME.cpp
+//		Purpose: Web application
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "APP_LONG_NAME.h"
+*** if database
+#include "Configuration.h"
+*** endif
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    APP_LONG_NAME::APP_LONG_NAME()
+//		Purpose: Constructor
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+APP_LONG_NAME::APP_LONG_NAME()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    APP_LONG_NAME::~APP_LONG_NAME()
+//		Purpose: Desctructor
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+APP_LONG_NAME::~APP_LONG_NAME()
+{
+}
+
+*** if database
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    APP_LONG_NAME::ChildStart(const Configuration &)
+//		Purpose: Called when a child is started
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+void APP_LONG_NAME::ChildStart(const Configuration &rConfiguration)
+{
+	mDatabase.Connect(rConfiguration.GetKeyValue("DatabaseDriver"),
+		rConfiguration.GetKeyValue("DatabaseConnection"), 2000 /* timeout */);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    APP_LONG_NAME::ChildFinish()
+//		Purpose: Called when the child process ends
+//		Created: CREATE_DATE
+//
+// --------------------------------------------------------------------------
+void APP_LONG_NAME::ChildFinish()
+{
+	mDatabase.Disconnect();
+}
+
+*** endif
+
+*** if database
+*** file APP_LONG_NAME.schema
+
+# sample table -- delete and replace with the schema you're using
+CREATE TABLE tItems (
+	fID `AUTO_INCREMENT_INT,
+	fString VARCHAR(64) NOT NULL
+);
+
+# sample data
+INSERT INTO tItems(fString) VALUES('Ice cubes');
+INSERT INTO tItems(fString) VALUES('Carrots');
+INSERT INTO tItems(fString) VALUES('Bananas');
+
+*** endif
+*** file APP_SHORT_NAME.cpp
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    APP_SHORT_NAME.cpp
+//		Purpose: Web application framework daemon starter
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "MainHelper.h"
+#include "Configuration.h"
+#include "autogen_webapp/APP_LONG_NAMEServer.h"
+*** if database
+#include "autogen_db/APP_LONG_NAME_schema.h"
+#include "DatabaseConnection.h"
+*** endif
+
+#include "MemLeakFindOn.h"
+
+int main(int argc, const char *argv[])
+{
+	MAINHELPER_START
+
+*** if database
+	// code to create and drop the database schema
+	// use as
+	//		APP_SHORT_NAME [create_db|drop_db] dbdriver_name connection_string
+	if(argc == 4)
+	{
+		// Connect to database
+		DatabaseConnection db;
+		db.Connect(std::string(argv[2]), std::string(argv[3]), 2000);
+		// Create or drop schema
+		if(strcmp(argv[1], "create_db") == 0)
+		{
+			APP_LONG_NAME_Create(db);
+			return 0;
+		}
+		else if(strcmp(argv[1], "drop_db") == 0)
+		{
+			APP_LONG_NAME_Drop(db);
+			return 0;
+		}
+		else
+		{
+			::printf("Usage: APP_SHORT_NAME [create_db|drop_db] dbdriver_name connection_string\n");
+			return 1;
+		}
+	}
+
+*** endif
+	// Start the web application
+	APP_LONG_NAMEServer server;
+	return server.Main(0, argc, argv);
+	
+	MAINHELPER_END
+}
+
+*** file APP_SHORT_NAME.conf
+
+AddressPrefix = http://localhost:1080
+
+*** if database
+DatabaseDriver = sqlite
+DatabaseConnection = APP_SHORT_NAME.sqlite
+
+*** endif
+Server
+{
+	PidFile = APP_SHORT_NAME.pid
+	ListenAddresses = inet:localhost:1080
+}
+
+*** mkdir Pages
+*** file Pages/Main.pl
+*** if no_database
+
+$page->add_text('TITLE','Main page');
+$page->add_text('PAGE','Your content here');
+*** endif
+*** if database
+use WebAppFramework::Unit::Database::Table;
+use Database::Query;
+
+
+$page->add_text('TITLE','Main page');
+
+# example query from the test data in the .schema file
+my $query = Database::Query->new(
+		'Name' => 'ListRetrieve',
+		'Statement' => 'SELECT fID,fString FROM tItems ORDER BY fString',
+		'Results' => 'int32_t ID,std::string String'
+	);
+my $list = WebAppFramework::Unit::Database::Table->new(
+		'Name' => 'entry',
+		'Query' => $query
+	);
+$page->add_unit('PAGE', $list);
+
+*** endif
+
+1;
+
+*** mkdir Languages
+*** file Languages/default.txt
+*** mkdir Templates
+*** file Templates/APP_LONG_NAMEMain.en.html
+<html>
+<head>
+<title>###TITLE### -- APP_LONG_NAME</title>
+</head>
+<body>
+###PAGE###
+<!--PageTemplate-Omit-Begin-->
+
+<!--Menu-Begin-->
+<table cellpadding=1 cellspacing=0 border=1>
+<tr><td><b>MENU</b></td></tr>
+<!--Menu/-->
+<!--Menu-Item-->
+<tr><td><a href="[URL]">[TEXT]</td></tr>
+<!--Menu/-->
+<!--Menu-ItemThisPage-->
+<tr><td>[TEXT]</td></tr>
+<!--Menu/-->
+<!--Menu-End-->
+</table>
+<!--Menu/-->
+
+<!--Table-Begin-->
+<table cellpadding=1 cellspacing=2 border=1>
+<!--Table/-->
+<!--Table-RowBegin-->
+<tr>
+<!--Table/-->
+<!--Table-CellBegin-->
+<td valign=top>
+<!--Table/-->
+<!--Table-CellEnd-->
+</td>
+<!--Table/-->
+<!--Table-RowEnd-->
+</tr>
+<!--Table/-->
+<tr>
+<!--Table-EmptyCell-->
+<td>&nbsp;</td>
+<!--Table/-->
+</tr>
+<!--Table-End-->
+</table>
+<!--Table/-->
+
+<!--Form-ErrorStart-->
+<font color=red>ERROR</font><br>
+Please correct the highlighted fields in the form below.<p>
+<!--Form/-->
+
+<!--Form-ErrorListStart-->
+<ul><li>
+<!--Form/-->
+<!--Form-ErrorListSeparate-->
+<li>
+<!--Form/-->
+<!--Form-ErrorListEnd-->
+</ul>
+<!--Form/-->
+
+<!--Form-Begin-->
+<table cellpadding=1 cellspacing=2 border=0>
+<!--Form/-->
+<!--Form-RowBegin-->
+<tr>
+<!--Form/-->
+<!--Form-CellBegin-Label-->
+<td valign=top align=right>
+<!--Form/-->
+<!--Form-CellEnd-Label-->
+</td>
+<!--Form/-->
+<!--Form-CellBegin-->
+<td valign=top>
+<!--Form/-->
+<!--Form-ErrorMarker-->
+<font color="red">**</font>
+<!--Form/-->
+<!--Form-InlineErrorStart-->
+<br><small>
+<!--Form/-->
+Inline error message
+<!--Form-InlineErrorEnd-->
+</small>
+<!--Form/-->
+<!--Form-CellEnd-->
+</td>
+<!--Form/-->
+<!--Form-RowEnd-->
+</tr>
+<!--Form/-->
+<tr>
+<!--Form-EmptyCell-->
+<td>&nbsp;</td>
+<!--Form/-->
+</tr>
+<!--Form-End-->
+</table>
+<!--Form/-->
+
+*** if database
+<!--DatabaseTable-Begin-->
+<table cellpadding=1 cellspacing=2 border=1>
+<!--DatabaseTable/-->
+<!--DatabaseTable-RowBegin-->
+<tr>
+<!--DatabaseTable/-->
+<!--DatabaseTable-HeadingCellBegin-->
+<td valign=top bgcolor="#dddddd">
+<!--DatabaseTable/-->
+Heading
+<!--DatabaseTable-HeadingCellEnd-->
+</td>
+<!--DatabaseTable/-->
+<!--DatabaseTable-DataCellBegin-->
+<td valign=top>
+<!--DatabaseTable/-->
+Data
+<!--DatabaseTable-DataCellEnd-->
+</td>
+<!--DatabaseTable/-->
+<!--DatabaseTable-RowEnd-->
+</tr>
+<!--DatabaseTable/-->
+<!--DatabaseTable-End-->
+</table>
+<!--DatabaseTable/-->
+
+*** endif
+<!--PageTemplate-Omit-End-->
+</body>
+</html>


Property changes on: box/features/codeforintegration/lib/webappframework/StarterTemplate.txt
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/webappframework/WAFFixedPoint.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFFixedPoint.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFFixedPoint.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,282 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFFixedPoint.cpp
+//		Purpose: Implement fixed point helper functions
+//		Created: 7/2/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "WAFUtilityFns.h"
+#include "autogen_WebAppFrameworkException.h"
+#include "WebAppForm.h"
+
+#include "MemLeakFindOn.h"
+
+// Avoid lots of mulitplications in loops
+static int fp_multiplier[] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUtility::ParseFixedPointDecimal(const std::string &, int32_t &, int)
+//		Purpose: See header for notes on parameters. Returns true if the string
+//				 parsed correctly.
+//		Created: 7/2/05
+//
+// --------------------------------------------------------------------------
+bool WAFUtility::ParseFixedPointDecimal(const std::string &rString, int32_t &rIntOut, int ScaleDigits)
+{
+	ASSERT(ScaleDigits >= 0);
+	if(ScaleDigits > WAFUTILITY_FIXEDPOINT_MAX_SCALE_DIGITS)
+	{
+		THROW_EXCEPTION(WebAppFrameworkException, FixedPointScaleTooLarge)
+	}
+
+	// Get string as simple c pointer
+	const char *string = rString.c_str();
+
+	// Parse the initial number
+	errno = 0;			// required on some platforms
+	char *end = 0;
+	int integerPart = ::strtol(string, &end, 10 /* base */);
+	if(errno != 0)
+	{
+		// Bad conversion -- no value or over/underflow, all bad.
+		return false;
+	}
+	ASSERT(end != 0);
+
+	// Try and parse the fractional part
+	int fractionalPart = 0;
+	if(*end != '\0')
+	{
+		// Skip whitespace
+		while(::isspace(*end)) {++end;}
+	
+		// Is there a fractional part to parse?
+		if(*end == '.')
+		{
+			// Yes, attempt to parse it
+			++end;	// over .
+			
+			// Skip whitespace
+			while(::isspace(*end)) {++end;}
+
+			// First, copy the digits to another buffer
+			// This is so if the user entered 1.0849724893274897238947239847892374
+			// the fractional part can be parsed without overflowing a normal integer
+			char fracBuffer[WAFUTILITY_FIXEDPOINT_MAX_SCALE_DIGITS + 4];
+			int fractionalDigits = 0;
+			for(fractionalDigits = 0; fractionalDigits < ScaleDigits; ++fractionalDigits)
+			{
+				if(*end >= '0' && *end <= '9')
+				{
+					fracBuffer[fractionalDigits] = *(end++);
+				}
+				else
+				{
+					break;
+				}
+			}
+			fracBuffer[fractionalDigits] = '\0';	// terminate
+			
+			// Check that the rest of the string is just digits followed by whitespace
+			{
+				const char *check = end;
+				while(*check >= '0' && *check <= '9')
+				{
+					++check;
+				}
+				while(::isspace(*check))
+				{
+					++check;
+				}
+				if(*check != '\0')
+				{
+					// Dodgy character in there somewhere
+					return false;
+				}
+			}
+			
+			errno = 0;	// required
+			fractionalPart = ::strtol(fracBuffer, 0, 10 /* base */);
+			if(errno != 0)
+			{
+				// A bad conversion
+				return false;
+			}
+
+			// Need to round up?
+			if(fractionalDigits == ScaleDigits && (*end >= '0' && *end <= '9'))
+			{
+				// Yes, there's more digits than specified
+				if(*end >= '5')
+				{
+					// And it means that the fractional part needs to be rounded up
+					++fractionalPart;
+				}
+			}
+			
+			// Need to multipy up the result
+			if(fractionalDigits < ScaleDigits)
+			{
+				// Not all the digits were supplied, so it needs to be multipled up
+				for(int l = fractionalDigits; l < ScaleDigits; ++l)
+				{
+					fractionalPart *= 10;
+				}
+			}
+		}
+		else if(*end != '\0')
+		{
+			// Failed to parse
+			return false;
+		}
+	}
+
+	// Generate the fixed point integer
+	int multipler = fp_multiplier[ScaleDigits];
+	if(integerPart >= 0)
+	{
+		rIntOut = (integerPart * multipler) + fractionalPart;
+	}
+	else
+	{
+		rIntOut = (integerPart * multipler) - fractionalPart;
+	}
+
+	// Check for overflow
+	if((rIntOut / multipler) != integerPart)
+	{
+		THROW_EXCEPTION(WebAppFrameworkException, FixedPointOverflow)
+	}
+
+	return true;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUtility::FixedPointDecimalToString(int32_t, std::string &, int, int)
+//		Purpose: See header for notes on paramters.
+//		Created: 7/2/05
+//
+// --------------------------------------------------------------------------
+void WAFUtility::FixedPointDecimalToString(int32_t Int, std::string &rStringOut, int ScaleDigits, int PlacesRequired)
+{
+	ASSERT(ScaleDigits >= 0);
+	if(ScaleDigits > WAFUTILITY_FIXEDPOINT_MAX_SCALE_DIGITS)
+	{
+		THROW_EXCEPTION(WebAppFrameworkException, FixedPointScaleTooLarge)
+	}
+	if(PlacesRequired == -1) PlacesRequired = ScaleDigits;
+	ASSERT(PlacesRequired >= 0);
+	ASSERT(PlacesRequired <= ScaleDigits);
+
+	// Generate divisor
+	int divisor = fp_multiplier[ScaleDigits];
+
+	// Generate format string first
+	char format[64];
+	::sprintf(format, "%%d.%%0%dd", ScaleDigits);
+
+	// Then generate the actual string
+	char output[64];
+	int fractional = Int % divisor;
+	if(fractional < 0) fractional = 0 - fractional;
+	int e = ::sprintf(output, format, Int / divisor, fractional);
+	
+	// How many zeros could be trimmed?
+	int zerosToTrim = ScaleDigits - PlacesRequired;
+	if(ScaleDigits == 0) {zerosToTrim = 1;}	// fix for the a special case of zero places
+	--e;
+	while(e >= 0 && output[e] == '0' && zerosToTrim > 0)
+	{
+		output[e--] = '\0';	// remove the zero
+		--zerosToTrim;
+	}
+	// Remove a trailing .
+	if(e >= 0 && output[e] == '.')
+	{
+		output[e] = '\0';
+	}
+	
+	// Return generated string
+	rStringOut = output;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUtility::IntepretNumberFieldFixedPoint(...)
+//		Purpose: Intepret an entered value on the form, as a fixed point value.
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+int8_t WAFUtility::IntepretNumberFieldFixedPoint(const std::string &rInput, int32_t &rNumberOut, int32_t BlankValue, bool BlankOK,
+	int32_t RangeBegin, int32_t RangeEnd, bool HaveRangeBegin, bool HaveRangeEnd, int ScaleDigits)
+{
+	// See if there is anything there
+	if(rInput.size() == 0)
+	{
+		if(BlankOK)
+		{
+			rNumberOut = BlankValue;
+			return WebAppForm::Valid;
+		}
+		else
+		{
+			return WebAppForm::NumberFieldErr_Blank;
+		}
+	}
+
+	// Convert the number
+	int32_t r = 0;
+	try
+	{
+		if(!WAFUtility::ParseFixedPointDecimal(rInput, r, ScaleDigits))
+		{
+			// Bad format
+			return WebAppForm::NumberFieldErr_Format;
+		}
+	}
+	catch(WebAppFrameworkException &e)
+	{
+		if(e.GetSubType() == WebAppFrameworkException::FixedPointOverflow)
+		{
+			// Overflowed, return a range error
+			return WebAppForm::NumberFieldErr_Range;
+		}
+		else
+		{
+			throw;
+		}
+	}
+
+	// Store the number decoded
+	rNumberOut = r;
+	
+	// Then range check...
+	if(HaveRangeBegin && (r < RangeBegin))
+	{
+		return WebAppForm::NumberFieldErr_Range;
+	}
+	if(HaveRangeEnd && (r > RangeEnd))
+	{
+		return WebAppForm::NumberFieldErr_Range;
+	}
+
+	return WebAppForm::Valid;
+}
+

Added: box/features/codeforintegration/lib/webappframework/WAFFormItemDate.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFFormItemDate.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFFormItemDate.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,141 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFFormItemDate.cpp
+//		Purpose: Form data field type for a date
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "WAFFormItemDate.h"
+#include "WebAppForm.h"
+
+#include "MemLeakFindOn.h"
+
+#define ALL_FIELDS_OBTAINED	0x7	// bits 0, 1, 2 set
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFFormItemDate::WAFFormItemDate()
+//		Purpose: Constructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFFormItemDate::WAFFormItemDate()
+	: mValidationState(0)
+{
+	mDate[Year] = 0;
+	mDate[Month] = 0;
+	mDate[Day] = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFFormItemDate::DataFromForm(int, const char *)
+//		Purpose: Accept data from the form, returning validation state
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+uint8_t WAFFormItemDate::DataFromForm(int Element, const char *Data, bool FieldIsOptional)
+{
+	// Make sure the element number is valid.
+	ASSERT(Element >= 0 && Element < 3);
+
+	// Only convert the data if it's not the empty string (ie nothing selected)
+	if(Data[0] != '\0')
+	{
+		// Already had the item?
+		if((mValidationState & (1<<Element)) != 0)
+		{
+			// Not good.
+			mValidationState = 0;
+			return WebAppForm::NotValid;
+		}
+	
+		// Attempt to decode the number
+		errno = 0;	// necessary on some platforms
+		int32_t v = ::strtol(Data, NULL, 10);
+		if((v == 0 && errno == EINVAL) || v == LONG_MIN || v == LONG_MAX)
+		{
+			// Bad number
+			return WebAppForm::NotValid;
+		}
+	
+		// Store the number, and mark it as obtained
+		mDate[Element] = v;
+		mValidationState |= 1 << Element;
+	}
+	else if(FieldIsOptional)
+	{
+		// Blank string entered, so might be valid (if nothing is filled in)
+		if(mValidationState == 0)
+		{
+			return WebAppForm::Valid;
+		}
+	}
+
+	return (mValidationState == ALL_FIELDS_OBTAINED)
+			?(CheckValidDate()?(WebAppForm::Valid):(WebAppForm::NotValid))
+			:(WebAppForm::NotValid);
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFFormItemDate::CheckValidDate()
+//		Purpose: Is the date a valid date?
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+bool WAFFormItemDate::CheckValidDate()
+{
+	// Year look OK?
+	if(mDate[Year] <= 0)
+	{
+		return false;
+	}
+
+	// Right then... is the month valid?
+	if(mDate[Month] < 1 || mDate[Month] > 12)
+	{
+		return false;
+	}
+	
+	// How many days are there in the month?
+	static const int monthDays[] = {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+	int maxDayInMonth = monthDays[mDate[Month]];
+	// Leap year?
+	if(mDate[Month] == 2)
+	{
+		// Leap year or not?
+		int y = mDate[Year];
+		if(
+			((y % 4) == 0)
+				&& ( ((y % 100) != 0) || ((y % 400) == 0) )
+			)
+		{
+			// It's a leap year
+			maxDayInMonth = 29;
+		}
+	}
+	
+	// Check day is valid too
+	if(mDate[Day] < 1 || mDate[Day] > maxDayInMonth)
+	{
+		return false;
+	}
+	
+	return true;
+}
+

Added: box/features/codeforintegration/lib/webappframework/WAFFormItemDate.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFFormItemDate.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFFormItemDate.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,47 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFFormItemDate.h
+//		Purpose: Form data field type for a date
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFFORMITEMDATE__H
+#define WAFFORMITEMDATE__H
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFFormItemDate
+//		Purpose: Class representing a date used by a form
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+class WAFFormItemDate
+{
+public:
+	WAFFormItemDate();
+	
+	enum
+	{
+		Year = 0, Month = 1, Day = 2
+	};
+	
+	// Get at data
+	int GetYear() const {return mDate[Year];}
+	int GetMonth() const {return mDate[Month];}
+	int GetDay() const {return mDate[Day];}
+	
+	// Accept data from the form, returning validation state
+	uint8_t DataFromForm(int Element, const char *Data, bool FieldIsOptional);
+	
+	bool CheckValidDate();
+	
+private:
+	int16_t mDate[3];
+	int8_t mValidationState;
+};
+
+#endif // WAFFORMITEMDATE__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,153 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale.cpp
+//		Purpose: Base class for locales
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <time.h>
+
+#include "WAFLocale.h"
+#include "autogen_WebAppFrameworkException.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale::WAFLocale()
+//		Purpose: Default constructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFLocale::WAFLocale()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale::~WAFLocale()
+//		Purpose: Default destructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFLocale::~WAFLocale()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale::FormatDate(int, int, int, int, std::string &)
+//		Purpose: Default date formatter, using dervied classes' month names.
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+void WAFLocale::FormatDate(int Style, int Year, int Month, int Day, std::string &rFormattedOut) const
+{
+	int checkedMonth = Month;
+	if(Month < 1 || Month > WAFLOCALE_MONTHS_IN_YEAR) checkedMonth = 0;
+	char str[64];	// know maximum length
+
+	switch(Style)
+	{
+	case WAFLocale::DateFormatLong:
+		{
+			::sprintf(str, "%d %s %d", Day, GetMonthName(checkedMonth), Year);
+			rFormattedOut = str;
+		}
+		break;
+	case WAFLocale::DateFormatShort:
+		{
+			::sprintf(str, "%02d/%02d/%02d", Day, Month, Year % 100);
+			rFormattedOut = str;
+		}
+		break;
+	default:
+		THROW_EXCEPTION(WebAppFrameworkException, BadDateFormat);
+		break;
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale::FormatDateTime(int, int, int, std::string &)
+//		Purpose: 
+//		Created: 9/1/05
+//
+// --------------------------------------------------------------------------
+void WAFLocale::FormatDateTime(int Style, int TimeFormat, int DateTime, std::string &rFormattedOut) const
+{
+	// Convert to elements
+	int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
+	switch(TimeFormat)
+	{
+	case WAFLocale::TimeFormatUNIXEpoch:
+		{
+			time_t clock = DateTime;
+			struct tm *decodedTime = ::gmtime(&clock);
+			if(time != 0)
+			{
+				year = decodedTime->tm_year + 1900;
+				month = decodedTime->tm_mon + 1;
+				day = decodedTime->tm_mday;
+				hour = decodedTime->tm_hour;
+				minute = decodedTime->tm_min;
+				second = decodedTime->tm_sec;
+			}
+		}
+		break;
+	case WAFLocale::TimeFormatYYYYMMDD:
+		{
+			year = DateTime / 10000;
+			month = (DateTime / 100) % 100;
+			day = DateTime % 100;
+		}
+		break;
+	default:
+		THROW_EXCEPTION(WebAppFrameworkException, BadTimeFormat);
+		break;
+	}
+	
+	// Format the result
+	switch(Style)
+	{
+	case WAFLocale::DateFormatLong:
+	case WAFLocale::DateFormatShort:
+		// Delegate to formatter
+		FormatDate(Style, year, month, day, rFormattedOut);
+		break;
+	case WAFLocale::DateTimeFormatLong:
+		{
+			FormatDate(WAFLocale::DateFormatLong, year, month, day, rFormattedOut);
+			char str[64];
+			::sprintf(str, ", %02d:%02d", hour, minute);
+			rFormattedOut += str;
+		}
+		break;
+	case WAFLocale::DateTimeFormatShort:
+		{
+			FormatDate(WAFLocale::DateFormatShort, year, month, day, rFormattedOut);
+			char str[64];
+			::sprintf(str, ", %02d:%02d", hour, minute);
+			rFormattedOut += str;
+		}
+		break;
+	default:
+		THROW_EXCEPTION(WebAppFrameworkException, BadDateFormat);
+		break;
+	}
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,109 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale.h
+//		Purpose: Base class for locales
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFLOCALE__H
+#define WAFLOCALE__H
+
+#include <string>
+
+#define WAFLOCALE_MONTHS_IN_YEAR		12
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFLocale
+//		Purpose: Base class for locales
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+class WAFLocale
+{
+public:
+	WAFLocale();
+	virtual ~WAFLocale();
+	
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WAFLocale::GetMonthList()
+	//		Purpose: Return an array of pointers to month names, for accessing
+	//				 using a 1 based index (ie as per human readable dates).
+	//		Created: 23/11/04
+	//
+	// --------------------------------------------------------------------------
+	virtual const char** GetMonthList() const = 0; // use 1 -- 12 as index on array
+
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WAFLocale::GetMonthName(int)
+	//		Purpose: Return a pointer to a month name, using 1 based index.
+	//				 If the month is invalid, a string will still be returned
+	//				 but it's text is undefined.
+	//		Created: 23/11/04
+	//
+	// --------------------------------------------------------------------------
+	virtual const char* GetMonthName(int MonthNumber) const = 0; // 1 -- 12
+
+	// Date formatting styles
+	enum
+	{
+		DateFormatLong = 0,
+		DateFormatShort = 1,
+		DateTimeFormatLong = 2,
+		DateTimeFormatShort = 3
+	};
+
+	// Time format
+	enum
+	{
+		TimeFormatUNIXEpoch = 0,
+		TimeFormatYYYYMMDD = 1			// ie y*10000+m*100+d
+	};
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WAFLocale::FormatDateTime(int, int, int, std::string &)
+	//		Purpose: Format a date and/or time in the specifed style.
+	//		Created: 23/11/04
+	//
+	// --------------------------------------------------------------------------
+	virtual void FormatDateTime(int Style, int TimeFormat, int DateTime, std::string &rFormattedOut) const;
+
+	// Slightly different name to avoid being hidden by derived classes
+	inline std::string FormatDateTimeR(int Style, int TimeFormat, int DateTime) const
+	{
+		std::string r;
+		FormatDateTime(Style, TimeFormat, DateTime, r);
+		return r;
+	}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WAFLocale::FormatDate(int, int, int, int, std::string &)
+	//		Purpose: Format a date in the specifed style.
+	//		Created: 23/11/04
+	//
+	// --------------------------------------------------------------------------
+	virtual void FormatDate(int Style, int Year, int Month, int Day, std::string &rFormattedOut) const;
+	
+	// Slightly different name to avoid being hidden by derived classes
+	inline std::string FormatDateR(int Style, int Year, int Month, int Day) const
+	{
+		std::string r;
+		FormatDate(Style, Year, Month, Day, r);
+		return r;
+	}
+};
+
+#endif // WAFLOCALE__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_en.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_en.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_en.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,92 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_en.cpp
+//		Purpose: English (UK) locale object
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "WAFLocale_en.h"
+
+#include "MemLeakFindOn.h"
+
+static const char* en_months[] = {
+	"UNDEFINED",
+	"January",
+	"February",
+	"March",
+	"April",
+	"May",
+	"June",
+	"July",
+	"August",
+	"September",
+	"October",
+	"November",
+	"December"
+};
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_en::WAFLocale_en()
+//		Purpose: Default constructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFLocale_en::WAFLocale_en()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_en::~WAFLocale_en()
+//		Purpose: Default destructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFLocale_en::~WAFLocale_en()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_en::GetMonthList()
+//		Purpose: As base class
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+const char** WAFLocale_en::GetMonthList() const
+{
+	return en_months;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_en::GetMonthName(int)
+//		Purpose: As base class
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+const char* WAFLocale_en::GetMonthName(int MonthNumber) const
+{
+	if(MonthNumber < 1 || MonthNumber > WAFLOCALE_MONTHS_IN_YEAR)
+	{
+		return en_months[0];
+	}
+	return en_months[MonthNumber];
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_en.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_en.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_en.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,34 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_en.h
+//		Purpose: English (UK) locale object
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFLOCALE_en__H
+#define WAFLOCALE_en__H
+
+#include "WAFLocale.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFLocale_en
+//		Purpose: English (UK) locale object
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+class WAFLocale_en : public WAFLocale
+{
+public:
+	WAFLocale_en();
+	virtual ~WAFLocale_en();
+
+	const char** GetMonthList() const;
+	const char* GetMonthName(int MonthNumber) const;
+};
+
+#endif // WAFLOCALE_en__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_it.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_it.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_it.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,92 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_it.cpp
+//		Purpose: Italian locale object
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "WAFLocale_it.h"
+
+#include "MemLeakFindOn.h"
+
+static const char* en_months[] = {
+	"UNDEFINED",
+	"Gennaio",
+	"Febbraio",
+	"Marzo",
+	"Aprile",
+	"Maggio",
+	"Giugno",
+	"Luglio",
+	"Agosto",
+	"Settembre",
+	"Ottobre",
+	"Novembre",
+	"Dicembre"
+};
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_it::WAFLocale_it()
+//		Purpose: Default constructor
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+WAFLocale_it::WAFLocale_it()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_it::~WAFLocale_it()
+//		Purpose: Default destructor
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+WAFLocale_it::~WAFLocale_it()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_it::GetMonthList()
+//		Purpose: As base class
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+const char** WAFLocale_it::GetMonthList() const
+{
+	return en_months;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_it::GetMonthName(int)
+//		Purpose: As base class
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+const char* WAFLocale_it::GetMonthName(int MonthNumber) const
+{
+	if(MonthNumber < 1 || MonthNumber > WAFLOCALE_MONTHS_IN_YEAR)
+	{
+		return en_months[0];
+	}
+	return en_months[MonthNumber];
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_it.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_it.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_it.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,34 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_it.h
+//		Purpose: Italian locale object
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFLOCALE_en__H
+#define WAFLOCALE_en__H
+
+#include "WAFLocale.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFLocale_it
+//		Purpose: English (UK) locale object
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+class WAFLocale_it : public WAFLocale
+{
+public:
+	WAFLocale_it();
+	virtual ~WAFLocale_it();
+
+	const char** GetMonthList() const;
+	const char* GetMonthName(int MonthNumber) const;
+};
+
+#endif // WAFLOCALE_en__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_jp.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_jp.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_jp.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,108 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_jp.cpp
+//		Purpose: Japanese locale object
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "WAFLocale_jp.h"
+
+#include "MemLeakFindOn.h"
+
+static const char* en_months[] = {
+	"UNDEFINED",
+	"1月",
+	"2月",
+	"3月",
+	"4月",
+	"5月",
+	"6月",
+	"7月",
+	"8月",
+	"9月",
+	"10月",
+	"11月",
+	"12月"
+};
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_jp::WAFLocale_jp()
+//		Purpose: Default constructor
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+WAFLocale_jp::WAFLocale_jp()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_jp::~WAFLocale_jp()
+//		Purpose: Default destructor
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+WAFLocale_jp::~WAFLocale_jp()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_jp::GetMonthList()
+//		Purpose: As base class
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+const char** WAFLocale_jp::GetMonthList() const
+{
+	return en_months;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_jp::GetMonthName(int)
+//		Purpose: As base class
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+const char* WAFLocale_jp::GetMonthName(int MonthNumber) const
+{
+	if(MonthNumber < 1 || MonthNumber > WAFLOCALE_MONTHS_IN_YEAR)
+	{
+		return en_months[0];
+	}
+	return en_months[MonthNumber];
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_jp::FormatDate(int, int, int, int, std::string &)
+//		Purpose: Override base class formatting
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+void WAFLocale_jp::FormatDate(int Style, int Year, int Month, int Day, std::string &rFormattedOut) const
+{
+	// Both styles are the same in this locale
+	char str[64];	// know maximum length
+	::sprintf(str, "%04d年%d月%d日", Year, Month, Day);
+	rFormattedOut = str;
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_jp.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_jp.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_jp.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,35 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_jp.h
+//		Purpose: Japanese locale object
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFLOCALE_en__H
+#define WAFLOCALE_en__H
+
+#include "WAFLocale.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFLocale_jp
+//		Purpose: Japanese locale object
+//		Created: 18/03/05
+//
+// --------------------------------------------------------------------------
+class WAFLocale_jp : public WAFLocale
+{
+public:
+	WAFLocale_jp();
+	virtual ~WAFLocale_jp();
+
+	const char** GetMonthList() const;
+	const char* GetMonthName(int MonthNumber) const;
+	virtual void FormatDate(int Style, int Year, int Month, int Day, std::string &rFormattedOut) const;
+};
+
+#endif // WAFLOCALE_en__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_nl.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_nl.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_nl.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,93 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_nl.cpp
+//		Purpose: Dutch locale object
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "WAFLocale_nl.h"
+
+#include "MemLeakFindOn.h"
+
+// NOTE: Dutch people do not capitalise their month names
+static const char* en_months[] = {
+	"UNDEFINED",
+	"januari",
+	"februari",
+	"maart",
+	"april",
+	"mei",
+	"juni",
+	"juli",
+	"augustus",
+	"september",
+	"oktober",
+	"november",
+	"december"
+};
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_nl::WAFLocale_nl()
+//		Purpose: Default constructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFLocale_nl::WAFLocale_nl()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_nl::~WAFLocale_nl()
+//		Purpose: Default destructor
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+WAFLocale_nl::~WAFLocale_nl()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_nl::GetMonthList()
+//		Purpose: As base class
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+const char** WAFLocale_nl::GetMonthList() const
+{
+	return en_months;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFLocale_nl::GetMonthName(int)
+//		Purpose: As base class
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+const char* WAFLocale_nl::GetMonthName(int MonthNumber) const
+{
+	if(MonthNumber < 1 || MonthNumber > WAFLOCALE_MONTHS_IN_YEAR)
+	{
+		return en_months[0];
+	}
+	return en_months[MonthNumber];
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFLocale_nl.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFLocale_nl.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFLocale_nl.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,34 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFLocale_nl.h
+//		Purpose: Dutch locale object
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFLocale_nl__H
+#define WAFLocale_nl__H
+
+#include "WAFLocale.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFLocale_nl
+//		Purpose: Dutch locale object
+//		Created: 23/11/04
+//
+// --------------------------------------------------------------------------
+class WAFLocale_nl : public WAFLocale
+{
+public:
+	WAFLocale_nl();
+	virtual ~WAFLocale_nl();
+
+	const char** GetMonthList() const;
+	const char* GetMonthName(int MonthNumber) const;
+};
+
+#endif // WAFLocale_nl__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFNumberField.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFNumberField.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFNumberField.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,122 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFNumberField.cpp
+//		Purpose: Implement functions for number fields
+//		Created: 7/2/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "WAFUtilityFns.h"
+#include "WebAppForm.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUtility::IntepretNumberField(...)
+//		Purpose: Intepret an entered value on the form.
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+int8_t WAFUtility::IntepretNumberField(const std::string &rInput, int32_t &rNumberOut, int32_t BlankValue, bool BlankOK,
+	int32_t RangeBegin, int32_t RangeEnd, bool HaveRangeBegin, bool HaveRangeEnd)
+{
+	bool nonNumCharsFound = false;
+	
+	// find all the number characters
+	std::string n;
+	for(std::string::const_iterator i(rInput.begin()); i != rInput.end(); ++i)
+	{
+		if(*i >= '0' && *i <= '9')
+		{
+			// OK character, add to string
+			n += *i;
+		}
+		else
+		{
+			if((*i == '-') && (n.size() == 0))
+			{
+				// - is OK, but only as the first character
+				n += '-';
+			}
+			else
+			{
+				// Bad character found
+				nonNumCharsFound = true;
+			}
+		}
+	}
+	
+	// Right then... see what we've got
+	if(n.size() == 0)
+	{
+		// Was it really blank?
+		if(nonNumCharsFound)
+		{
+			return WebAppForm::NumberFieldErr_FormatBlank;
+		}
+		else
+		{
+			// Yes, was blank, is this an error or not?
+			if(BlankOK)
+			{
+				rNumberOut = BlankValue;
+				return WebAppForm::Valid;
+			}
+			else
+			{
+				return WebAppForm::NumberFieldErr_Blank;
+			}
+		}
+	}
+	
+	// Convert the number
+	errno = 0;	// necessary on some platforms
+	int32_t r = ::strtol(n.c_str(), 0, 10);
+
+	// Error check
+	if(r == 0 && errno == EINVAL)
+	{
+		// Shouldn't get this as the number only contains valid characters
+		return WebAppForm::NumberFieldErr_Format;
+	}
+
+	// Range check from strtol
+	if((r == LONG_MIN || r == LONG_MAX) && errno == ERANGE)
+	{
+		return WebAppForm::NumberFieldErr_Range;
+	}
+
+	// Store the number decoded
+	rNumberOut = r;
+	
+	// Check format
+	if(nonNumCharsFound)
+	{
+		return WebAppForm::NumberFieldErr_Format;
+	}
+
+	// Then range check...
+	if(HaveRangeBegin && (r < RangeBegin))
+	{
+		return WebAppForm::NumberFieldErr_Range;
+	}
+	if(HaveRangeEnd && (r > RangeEnd))
+	{
+		return WebAppForm::NumberFieldErr_Range;
+	}
+
+	return WebAppForm::Valid;
+}
+

Added: box/features/codeforintegration/lib/webappframework/WAFPhoneNumber.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFPhoneNumber.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFPhoneNumber.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,99 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFPhoneNumber.cpp
+//		Purpose: Implement functions for phone number fields
+//		Created: 11/2/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <ctype.h>
+
+#include "WAFUtilityFns.h"
+
+#include "MemLeakFindOn.h"
+
+#define MIN_DIGITS_IN_PHONE_NUMBER	7
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUtility::ValidatePhoneNumber(const std::string &, std::string)
+//		Purpose: Return true if the field is a valid phone number. Also, turn the
+//				 phone number into a canonocial form.
+//		Created: 11/2/05
+//
+// --------------------------------------------------------------------------
+bool WAFUtility::ValidatePhoneNumber(const std::string &rInput, std::string &rCanonicalPhoneNumberOut)
+{
+	// For output
+	std::string canonical;
+	
+	// Get string
+	const char *phone = rInput.c_str();
+	
+	// Skip leading whitespace
+	while(::isspace(*phone))
+	{
+		++phone;
+	}
+
+	// A plus is only allowed at the beginning, so need special validation
+	if(*phone == '+')
+	{
+		canonical += '+';
+		++phone;
+	}
+	
+	// Loop though scanning characters
+	bool whitespace = false;
+	int numDigits = 0;
+	while(*phone != '\0')
+	{
+		if(::isspace(*phone))
+		{
+			// Flag for whitespace later (to contract multiple spaces into one)
+			whitespace = true;
+		}
+		else if((*phone >= '0' && *phone <= '9') || *phone == '.' || *phone == ',')
+		{
+			// Count digits
+			if(*phone >= '0' && *phone <= '9')
+			{
+				++numDigits;
+			}
+			
+			// Any whitespace to add?
+			if(whitespace)
+			{
+				canonical += ' ';
+				whitespace = false;
+			}
+			
+			// Add character
+			canonical += *phone;
+		}
+		else
+		{
+			// Bad phone number
+			return false;
+		}
+		
+		++phone;
+	}
+	
+	// Are there enough digits?
+	if(numDigits < MIN_DIGITS_IN_PHONE_NUMBER)
+	{
+		return false;
+	}
+
+	// Return where phone number is valid
+	rCanonicalPhoneNumberOut = canonical;
+	return true;
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFUKPostcode.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFUKPostcode.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFUKPostcode.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,176 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFUKPostcode.cpp
+//		Purpose: UK postcode validation and handling
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+#include "WAFUKPostcode.h"
+
+/*
+Valid formats:
+
+Outcode	Incode	Example
+AN		NAA		M1 1AA
+ANN		NAA		M60 1NW
+AAN		NAA		CR2 6XH
+AANN	NAA		DN55 1PT
+ANA		NAA		W1P 1HQ
+AANA	NAA		EC1A 1B
+(A=alpha, N=numeric)
+*/
+#define MIN_POSTCODE_LENGTH		5
+#define MAX_POSTCODE_LENGTH		7
+#define	INCODE_LENGTH			3
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUKPostcode::WAFUKPostcode()
+//		Purpose: Constructor
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+WAFUKPostcode::WAFUKPostcode()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUKPostcode::~WAFUKPostcode()
+//		Purpose: Destructor
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+WAFUKPostcode::~WAFUKPostcode()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUKPostcode::ParseAndValidate(const std::string &rInput)
+//		Purpose: Parse a postcode from a user, returning true if it's valid.
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+bool WAFUKPostcode::ParseAndValidate(const std::string &rInput)
+{
+	// Make sure everything is blank (so validation checks later work)
+	mOutcode.erase();
+	mIncode.erase();
+	
+	// Look at the input
+	const char *in = rInput.c_str();
+	
+	// Buffers for the normalised postcode
+	char postcode[MAX_POSTCODE_LENGTH + 1];
+	bool postcodeCharIsNumber[MAX_POSTCODE_LENGTH + 1];
+	
+	#define IS_IT_TOO_LONG	if(pcLen >= MAX_POSTCODE_LENGTH) return false;
+	
+	// Copy in the data, checking and transforming...
+	int pcLen = 0;
+	while(*in != '\0')
+	{
+		char i = *in;
+		if(i == ' ' || i == '\t' || i == '\n')
+		{
+			// ignore this one
+		}
+		else if(i >= '0' && i <= '9')
+		{
+			// Number...
+			IS_IT_TOO_LONG
+			postcodeCharIsNumber[pcLen] = true;
+			postcode[pcLen] = i;
+			++pcLen;
+		}
+		else if(i >= 'A' && i <= 'Z')
+		{
+			// Letter, uppercase...
+			IS_IT_TOO_LONG
+			postcodeCharIsNumber[pcLen] = false;
+			postcode[pcLen] = i;
+			++pcLen;
+		}
+		else if(i >= 'a' && i <= 'z')
+		{
+			// Letter, lowercase...
+			IS_IT_TOO_LONG
+			postcodeCharIsNumber[pcLen] = false;
+			postcode[pcLen] = i - ('a' - 'A');
+			++pcLen;
+		}
+		else
+		{
+			return false;		// bad char
+		}
+	
+		++in;
+	}
+	
+	// quick check for it being too short
+	if(pcLen < MIN_POSTCODE_LENGTH) return false;
+	
+	// Validate formats
+	// First the incode, because it's easy
+	if(postcodeCharIsNumber[pcLen - 3] != true
+		|| postcodeCharIsNumber[pcLen - 2] != false
+		|| postcodeCharIsNumber[pcLen - 1] != false)
+	{
+		return false;
+	}
+	
+	// Then the various possibilities
+	if(postcodeCharIsNumber[0] != false)
+	{
+		return false;
+	}
+	if(pcLen == 5 && postcodeCharIsNumber[1] != true)
+	{
+		return false;
+	}
+	if(pcLen == 6)
+	{
+		if(postcodeCharIsNumber[1] == false && postcodeCharIsNumber[2] == false)
+		{
+			return false;
+		}
+	}
+	if(pcLen == 7)
+	{
+		if(postcodeCharIsNumber[1] != false || postcodeCharIsNumber[2] != true)
+		{
+			return false;
+		}
+	}
+	
+	// So it looks good then, store
+	mOutcode.assign(postcode, pcLen - INCODE_LENGTH);
+	mIncode.assign(postcode + pcLen - INCODE_LENGTH, INCODE_LENGTH);
+	
+	// Success
+	return true;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WAFUKPostcode::NormalisedPostcode()
+//		Purpose: Return a normalised postcode, or the empty string if it isn't valid
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+std::string WAFUKPostcode::NormalisedPostcode() const
+{
+	if(!IsValid()) return std::string();
+	
+	return mOutcode + " " + mIncode;
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WAFUKPostcode.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFUKPostcode.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFUKPostcode.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,42 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFUKPostcode.h
+//		Purpose: UK postcode validation and handling
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFUKPOSTCODE__H
+#define WAFUKPOSTCODE__H
+
+#include <string>
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WAFUKPostcode
+//		Purpose: UK postcode validation and handling
+//		Created: 31/8/05
+//
+// --------------------------------------------------------------------------
+class WAFUKPostcode
+{
+public:
+	WAFUKPostcode();
+	~WAFUKPostcode();
+
+	bool ParseAndValidate(const std::string &rInput);
+	
+	bool IsValid() const {return mOutcode.size() > 0 && mIncode.size() > 0;}
+	const std::string &Outcode() const {return mOutcode;}
+	const std::string &Incode() const {return mIncode;}
+	std::string NormalisedPostcode() const;
+
+private:
+	std::string mOutcode;
+	std::string mIncode;
+};
+
+#endif // WAFUKPOSTCODE__H
+

Added: box/features/codeforintegration/lib/webappframework/WAFUtilityFns.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WAFUtilityFns.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WAFUtilityFns.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,38 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WAFUtilityFns.h
+//		Purpose: Define utility functions for the web app framework
+//		Created: 7/2/05
+//
+// --------------------------------------------------------------------------
+
+#ifndef WAFUTILITYFNS__H
+#define WAFUTILITYFNS__H
+
+#include <string>
+
+#define WAFUTILITY_FIXEDPOINT_MAX_SCALE_DIGITS 8
+
+namespace WAFUtility
+{
+	// ScaleDigits -- number of digits after decimal point.
+	//					ie 2 implies a precision of 0.01, and multiplication factor of 100
+	// PlacesRequired -- number of decimal places required in output.
+	//					eg with ScaleDigits=3 and PlacesRequired=2, 10004 -> "1.0004", 10030 -> "1.003", 10200 -> "1.02", 11000 -> "1.10"
+	bool ParseFixedPointDecimal(const std::string &rString, int32_t &rIntOut, int ScaleDigits);
+	void FixedPointDecimalToString(int32_t Int, std::string &rStringOut, int ScaleDigits, int PlacesRequired = -1);
+	
+	// Number fields		
+	int8_t IntepretNumberField(const std::string &rInput, int32_t &rNumberOut, int32_t BlankValue, bool BlankOK,
+		int32_t RangeBegin, int32_t RangeEnd, bool HaveRangeBegin, bool HaveRangeEnd);
+	// In the fixed point file, for linkage reasons
+	int8_t IntepretNumberFieldFixedPoint(const std::string &rInput, int32_t &rNumberOut, int32_t BlankValue, bool BlankOK,
+		int32_t RangeBegin, int32_t RangeEnd, bool HaveRangeBegin, bool HaveRangeEnd, int ScaleDigits);
+
+	// Validation
+	bool ValidatePhoneNumber(const std::string &rInput, std::string &rCanonicalPhoneNumberOut);
+};
+
+#endif // WAFUTILITYFNS__H
+

Added: box/features/codeforintegration/lib/webappframework/WebAppForm.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppForm.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppForm.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,177 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WebAppForm.cpp
+//		Purpose: Base class for web application forms
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "WebAppForm.h"
+#include "autogen_WebAppFrameworkException.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppForm::WebAppForm()
+//		Purpose: Constructor
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+WebAppForm::WebAppForm()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppForm::WebAppForm(const WebAppForm &)
+//		Purpose: Copy constructor
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+WebAppForm::WebAppForm(const WebAppForm &rToCopy)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppForm::~WebAppForm()
+//		Purpose: Destructor
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+WebAppForm::~WebAppForm()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppForm::operator=(const WebAppForm &)
+//		Purpose: Assignment operator
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+WebAppForm &WebAppForm::operator=(const WebAppForm &rToCopy)
+{
+	return *this;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppFormCustomErrors::WebAppFormCustomErrors()
+//		Purpose: Constructor
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+WebAppFormCustomErrors::WebAppFormCustomErrors()
+	: mpErrorSeparator(0),
+	  mppTranslatedStrings(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppFormCustomErrors::WebAppFormCustomErrors(const WebAppFormCustomErrors &)
+//		Purpose: Copy constructor
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+WebAppFormCustomErrors::WebAppFormCustomErrors(const WebAppFormCustomErrors &rToCopy)
+	: WebAppForm(rToCopy),
+	  mpErrorSeparator(rToCopy.mpErrorSeparator),
+	  mppTranslatedStrings(rToCopy.mppTranslatedStrings)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppFormCustomErrors::~WebAppFormCustomErrors()
+//		Purpose: Destructor
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+WebAppFormCustomErrors::~WebAppFormCustomErrors()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppFormCustomErrors::operator=(const WebAppFormCustomErrors &)
+//		Purpose: Assignment operator
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+WebAppFormCustomErrors &WebAppFormCustomErrors::operator=(const WebAppFormCustomErrors &rToCopy)
+{
+	WebAppForm::operator=(rToCopy);
+	mpErrorSeparator = rToCopy.mpErrorSeparator;
+	mppTranslatedStrings = rToCopy.mppTranslatedStrings;
+	return *this;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppFormCustomErrors::AddError(const char *)
+//		Purpose: Adds an error string to the list of errors.
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+void WebAppFormCustomErrors::AddError(const char *Error)
+{
+	// Separator?
+	if(mErrorText.size() != 0 && mpErrorSeparator != 0)
+	{
+		mErrorText += mpErrorSeparator;
+	}
+	
+	// Add text
+	if(Error != 0)
+	{
+		mErrorText += Error;
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebAppFormCustomErrors::GetTranslatedStrings()
+//		Purpose: Returns the list of translated strings, exceptions if none have been set
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+const char **WebAppFormCustomErrors::GetTranslatedStrings() const
+{
+	if(mppTranslatedStrings == 0)
+	{
+		THROW_EXCEPTION(WebAppFrameworkException, TranslatedStringsNotSet);
+	}
+
+	return mppTranslatedStrings;
+}
+

Added: box/features/codeforintegration/lib/webappframework/WebAppForm.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppForm.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppForm.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,118 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WebAppForm.h
+//		Purpose: Base class for web application forms
+//		Created: 17/4/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WEBAPPFORM__H
+#define WEBAPPFORM__H
+
+#include <string>
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WebAppForm
+//		Purpose: Base class for Form objects
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+class WebAppForm
+{
+public:
+	WebAppForm();
+	WebAppForm(const WebAppForm &rToCopy);
+	~WebAppForm();
+	WebAppForm &operator=(const WebAppForm &rToCopy);
+
+	// Error codes
+	enum
+	{
+		Valid = 0,
+		NotValid = 255,
+		Error1 = 1,
+		Error2 = 2,
+		Error3 = 3,
+		Error4 = 4,
+		NumberFieldErr_Range = 1,
+		NumberFieldErr_Format = 2,
+		NumberFieldErr_Blank = 3,
+		NumberFieldErr_FormatBlank = 4
+	};
+	
+	// Misc constants
+	enum
+	{
+		NoChoiceMade = -1
+	};
+};
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WebAppFormCustomErrors
+//		Purpose: Base class for Form objects which emit custom errors
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+class WebAppFormCustomErrors : public WebAppForm
+{
+public:
+	WebAppFormCustomErrors();
+	WebAppFormCustomErrors(const WebAppFormCustomErrors &rToCopy);
+	~WebAppFormCustomErrors();
+	WebAppFormCustomErrors &operator=(const WebAppFormCustomErrors &rToCopy);
+
+	// Functions for autogen code:
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WebAppFormCustomErrors::SetStrings(const char *, const char **)
+	//		Purpose: Set the strings to use -- separator string, plus the translated strings table 
+	//		Created: 19/4/04
+	//
+	// --------------------------------------------------------------------------
+	void SetStrings(const char *pErrorSeparator, const char **ppTranslatedStrings)
+	{
+		mpErrorSeparator = pErrorSeparator;
+		mppTranslatedStrings = ppTranslatedStrings;
+	}
+
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WebAppFormCustomErrors::HaveErrorText()
+	//		Purpose: Does this form have extra error text?
+	//		Created: 19/4/04
+	//
+	// --------------------------------------------------------------------------
+	bool HaveErrorText() const {return mErrorText.size() != 0;}
+	
+	// --------------------------------------------------------------------------
+	//
+	// Function
+	//		Name:    WebAppFormCustomErrors::GetErrorText()
+	//		Purpose: Get the extra error text
+	//		Created: 19/4/04
+	//
+	// --------------------------------------------------------------------------
+	const std::string &GetErrorText() const {return mErrorText;}
+	
+protected:
+	// Functions for derived classes
+	
+	void AddError(const char *Error);
+	const char **GetTranslatedStrings() const;
+
+private:
+	std::string mErrorText;
+	const char *mpErrorSeparator;
+	const char **mppTranslatedStrings;
+};
+
+#endif // WEBAPPFORM__H
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/ArgumentAdaptor.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/ArgumentAdaptor.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/ArgumentAdaptor.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,108 @@
+package WebAppFramework::ArgumentAdaptor;
+use strict;
+
+#	'Target' => $object,
+#	'Source' => $unit,
+#	'DefaultSourceObject' => 'objectname',
+#	'Args' => ['name' => 'objectname.Name', 'name2' => $cppvariable]
+#
+#	$adaptor->generate_call('query', 'xyz = query.Do', 'rConnection'); # method name and handle return, preceeding arguments
+
+#	$object must support...
+#		get_arguments();	# array of CppVariables
+#		some_arguments_may_be_null();	# true if some are null
+#		get_arguments_null();	# only needs to be implemented if above fn is true
+
+sub new
+{
+	my ($type, @params) = @_;
+
+	my $self = {@params};
+	bless $self;
+
+	# return object
+	$self
+}
+
+
+sub generate_call
+{
+	my ($self, $function_base, $additional_args) = @_;
+	
+	my @fn_args = $$self{'Target'}->get_arguments();
+	my $has_nulls = 0;
+	my @args_null;
+	if($$self{'Target'}->some_arguments_may_be_null())
+	{
+		@args_null = $$self{'Target'}->get_arguments_null();
+		for(@args_null) {$has_nulls = 1 if $_}
+	}
+
+	# for each of the arguments, locate the source for that variable
+	my %arg_source_info;
+	%arg_source_info = (@{$$self{'Args'}}) if exists $$self{'Args'} && ref($$self{'Args'});
+	my @arg_source;
+	for(my $n = 0; $n <= $#fn_args; $n++)
+	{
+		# is there a source specified for this argument?
+		my $s;
+		my $aname = $fn_args[$n]->name();
+		if(exists $arg_source_info{$aname})
+		{
+			# yes, use it
+			$s = $arg_source_info{$aname}; 
+		}
+		else
+		{
+			# no, generate a source page variable name using the default source
+			unless(exists $$self{'DefaultSourceObject'})
+			{
+				die "Trying to find an argument source, $aname not sound in Args, and no DefaultSourceObject specified to ArgumentAdaptor"
+			}
+			$s = $$self{'DefaultSourceObject'} . '.' . $aname;
+		}
+		# add to array
+		push @arg_source,$$self{'Source'}->get_variable($s);
+	}
+
+	# start generating the C++ -- need to convert any arguments which need to be passed by pointer?
+	my $call;
+	if($has_nulls)
+	{
+		# create local var of anything which may be null
+		for(my $n = 0; $n <= $#fn_args; $n++)
+		{
+			if($args_null[$n])
+			{
+				my $comp = $fn_args[$n]->is_composite_type();
+				my $ttype = $fn_args[$n]->type();
+				$call .= $ttype .' a'.$n.($comp?'(':' = ').$arg_source[$n]->convert_to($ttype).($comp?");\n":";\n");
+			}
+		}
+	}
+
+	# function call
+	$call .= $function_base . '(' . $additional_args . (($additional_args ne '' && $#fn_args >= 0)?', ':'');
+
+	# generate arguments
+	my $args;
+	for(my $n = 0; $n <= $#fn_args; $n++)
+	{
+		$args .= ', ' if $args ne '';
+		$args .= $arg_source[$n]->convert_to($fn_args[$n]->type())
+	}
+
+	# add args and terminate function call
+	$call .= $args.");\n";
+
+	if($has_nulls)
+	{
+		# indent nicely
+		$call =~ s/^/\t/mg;
+		# surround in a block
+		$call = "{\n".$call."}\n";
+	}
+	$call
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/DateAndTime.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/DateAndTime.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/DateAndTime.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,37 @@
+package WebAppFramework::DateAndTime;
+use strict;
+
+# This module provides utility functions relating to date and time
+
+
+# make_YMD_params($unit_ref, @standard_specification)
+#
+# Takes a date specification, and turns it into a year, month, day arguments.
+#
+sub make_YMD_params_array
+{
+	my ($unit, $type, @args) = @_;
+	
+	if($type eq 'ymd')
+	{
+		die "'ymd' specifier for dates doesn't have the correct number of arguments"
+			unless $#args == 2;
+		return map {$unit->get_variable($_)->convert_to('int32_t')} @args;
+	}
+	elsif($type eq 'WAFFormItemDate')
+	{
+		my $v = $unit->get_variable($args[0])->convert_to('WAFFormItemDate');
+		return ("$v.GetYear()", "$v.GetMonth()", "$v.GetDay()");
+	}
+	else
+	{
+		die "Unknown date format specifier '$type'"
+	}
+}
+sub make_YMD_params
+{
+	join(', ',make_YMD_params_array(@_))
+}
+
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/FixedPoint.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/FixedPoint.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/FixedPoint.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,28 @@
+package WebAppFramework::FixedPoint;
+use strict;
+
+# This module provides utility functions relating to fixed point numbers
+
+sub write_fixed_point_value
+{
+	# Sort out the parameters
+	my ($output,$value,$scaleDigits,$displayPlaces) = @_;
+	$scaleDigits = int($scaleDigits);
+	$displayPlaces = $scaleDigits unless defined $displayPlaces;
+	$displayPlaces = int($displayPlaces);
+	
+	# convert the value to an integer
+	my $v = $value->convert_to('int32_t');
+	
+	# write the display code
+	$output->write_code(<<__E);
+		{
+			std::string formatted;
+			WAFUtility::FixedPointDecimalToString($v, formatted, $scaleDigits, $displayPlaces);
+			rResponse.Write(formatted.c_str(), formatted.size());
+		}
+__E
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/LanguageFile.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/LanguageFile.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/LanguageFile.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,246 @@
+package WebAppFramework::LanguageFile;
+use strict;
+use Symbol;
+use Fcntl qw(LOCK_EX SEEK_SET SEEK_CUR);
+use Digest::MD5 qw(md5_hex);
+
+use constant FILEHANDLE => 0;
+use constant CONTENTS => 1;
+use constant LOOKUP => 2;
+use constant CURRENT_PAGE => 3;
+use constant DEFAULT_LANG => 4;
+
+use constant KEY => 0;
+use constant PAGES => 1;
+use constant TRANSLATED => 2;
+use constant ORIGINAL => 3;
+
+sub new
+{
+	my ($type, $filename, $current_page, $default_language_object) = @_;
+
+	my $self = [open_file($filename), [], {}, $current_page, $default_language_object];
+	bless $self;
+	$self
+}
+
+# When loading a language file, the current_page parameter is used to remove
+# all mentions of a page from the usage list on loading, and for the 
+# update_and_translate method to update the usage list. This ensures that
+# the usages lists remain accurate.
+#
+# The default language is used to obtain default translations. If a string
+# is neither in this language or the default language, it will be added to both.
+#
+# A normal array with a hash array for lookup is used, rather than just a simple
+# and more efficient hash array, so that the ordering of the file is maintained.
+#
+sub load
+{
+	my ($type, $filename, $current_page, $default_language_object, $dont_suppress_usage) = @_;
+
+	# Read file into two arrays
+	my $contents = [];
+	my $lookup = {};
+	
+	# open file
+	my $f = open_file($filename);
+	# read contents
+	my $key = '';
+	my $in_pages = '';
+	my $text = '';
+	my $original = '';
+	while(<$f>)
+	{
+		if(m/\A========\s*\Z/)
+		{
+			# remove extra space from the text
+			$text =~ s/\A\n//;   # only the first newline
+			$text =~ s/\n\n\Z//; # last two newlines
+		
+			# add entry
+			if($key ne '')
+			{
+				my @used;
+				if($dont_suppress_usage)
+				{
+					@used = split /\s+/,$in_pages
+				}
+				else
+				{
+					@used = map {($_ eq $current_page)?():$_} split /\s+/,$in_pages;
+				}
+				push @$contents,[$key, [@used], $text, $original];
+				$$lookup{$key} = $#{$contents};
+			}
+			
+			# reset
+			$key = '';
+			$in_pages = '';
+			$text = '';
+			$original = '';
+		}
+		elsif(m/\A#\s+(.+)\n?\Z/)
+		{
+			$key = $1
+		}
+		elsif(m/\A@\s+(.*)\n?\Z/)
+		{
+			$in_pages = $1
+		}
+		elsif(m/\A>\s?(.*)\n?\Z/)
+		{
+			$original .= "\n" unless $original eq '';
+			$original .= $1
+		}
+		else
+		{
+			$text .= $_;
+		}
+	}
+
+	# create object and return
+	my $self = [$f, $contents, $lookup, $current_page, $default_language_object];
+	bless $self;
+	$self
+}
+
+sub open_file
+{
+	my ($filename) = @_;
+
+	# open file
+	my $f = gensym;
+	if(-e $filename)
+	{
+		# open read/write
+		open $f,'+<'.$filename or die "Can't open $filename for reading/writing language";
+	}
+	else
+	{
+		# open write only
+		open $f,'>'.$filename or die "Can't open $filename for writing language";
+	}
+	# lock file
+	flock($f,LOCK_EX);
+	seek($f,0,SEEK_SET);
+	
+	# return the file handle
+	$f;
+}
+
+sub translate_and_update
+{
+	my ($self, $source_text) = @_;
+	
+	# don't let things without any non-whitespace get into the translated files
+	if($source_text !~ m/\S/)
+	{
+		return '';
+	}
+
+	# process the given text into the key and adjust the source text if necessary
+	my $key;
+	my $text;
+	my $is_user_keyed = 0;	# whether the user specified the key
+	if($source_text =~ m/\A\s*\*(.+?)\*(.*)\Z/s)
+	{
+		# text uses the *key*text notation
+		$key = $1 . '-u';
+		$text = $2;
+		$is_user_keyed = 1;
+	}
+	elsif(length($source_text) > 32)
+	{
+		# long text, use a hash of the key
+		$key = md5_hex($source_text) . '-h';
+		$text = $source_text;
+	}
+	else
+	{
+		# short text, use a processed form of the key
+		$key = $source_text;
+		$key =~ s/\s+/_/gs;
+		$key =~ s/(\A_|_\Z)//g;
+		$key .= '-t';
+		$text = $source_text;
+	}
+
+	# exists?
+	if(exists ${$$self[LOOKUP]}{$key})
+	{
+		# yes.
+		my $index = ${$$self[LOOKUP]}{$key};
+		my $entry = ${$$self[CONTENTS]}[$index];
+		
+		# Update usage list
+		my $u = 0;
+		for(@{$$entry[PAGES]})
+		{
+			if($_ eq $$self[CURRENT_PAGE])
+			{
+				$u = 1;
+				last
+			}
+		}
+		unless($u)
+		{
+			push @{$$entry[PAGES]},$$self[CURRENT_PAGE]
+		}
+
+		if($is_user_keyed)
+		{
+			# update the original text
+			$$entry[ORIGINAL] = $text;
+			# if the default language, update the translated text too
+			unless(defined $$self[DEFAULT_LANG])
+			{
+				$$entry[TRANSLATED] = $text;
+			}
+		}
+		
+		# return translation
+		return $$entry[TRANSLATED]
+	}
+	
+	# so, doesn't exist. The default translation is either the translation
+	# from the default language, or the text itself. (This approach automatically
+	# adds the string to the default language.)
+	# NOTE: Use the source text when adding to the default language, so the
+	# keys are maintained.
+	my $default_translation = (defined $$self[DEFAULT_LANG])?($$self[DEFAULT_LANG]->translate_and_update($source_text)):$text;
+	
+	push @{$$self[CONTENTS]},[$key, [$$self[CURRENT_PAGE]], $default_translation, $text];
+	$$self[LOOKUP]{$key} = $#{$$self[CONTENTS]};
+
+	# and return that default
+	$default_translation
+}
+
+sub save
+{
+	my ($self) = @_;
+
+	# retrieve file handle, and seek to the beginning
+	my $f = $$self[FILEHANDLE];
+	seek($f,0,SEEK_SET);
+	# truncate the file to zero bytes
+	truncate $f,0;
+	
+	for(@{$$self[CONTENTS]})
+	{
+		my $o = $$_[ORIGINAL];
+		$o =~ s/\s+\Z//;
+		$o =~ s/^/> /mg;
+		print $f '# ',
+			$$_[KEY],"\n@ ",
+			join (' ',sort(@{$$_[PAGES]})),"\n",
+			$o,"\n\n",
+			$$_[TRANSLATED],"\n\n========\n"
+	}
+	close $f
+}
+
+1;
+
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/en.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/en.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/en.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,35 @@
+package WebAppFramework::Locale::en;
+use strict;
+use base 'WebAppFramework::Locale';
+use vars '%_information';
+
+%_information = (
+	# text translations
+	'Day' => 'Day',
+	'Month' => 'Month',
+	'Year' => 'Year',
+	
+	# Note: Preferred coding style for accessing lists of months etc is to look them up
+	# using the C++ object at runtime.
+	'MonthList' => 'January|February|March|April|May|June|July|August|September|October|November|December',
+	'DayList' => 'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday',
+	
+	# other information
+	'FormItemDateOrdering' => 'DMY',	# how fields in a date form item are ordered
+);
+
+sub get
+{
+	my ($self,$item) = @_;
+	if(exists $_information{$item})
+	{
+		return $_information{$item}
+	}
+	else
+	{
+		die "'$item' is an unrecognised locale item"
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/it.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/it.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/it.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,35 @@
+package WebAppFramework::Locale::it;
+use strict;
+use base 'WebAppFramework::Locale';
+use vars '%_information';
+
+%_information = (
+	# text translations
+	'Day' => 'Giorno',
+	'Month' => 'Mese',
+	'Year' => 'Anno',
+	
+	# Note: Preferred coding style for accessing lists of months etc is to look them up
+	# using the C++ object at runtime.
+	'MonthList' => 'Gennaio|Febbraio|Marzo|Aprile|Maggio|Giugno|Luglio|Agosto|Settembre|Ottobre|Novembre|Dicembre',
+	'DayList' => 'Lunedì|Martedì|Mercoledì|Giovedì|Venerdì|Sabato|Domenica',
+
+	# other information
+	'FormItemDateOrdering' => 'DMY',	# how fields in a date form item are ordered
+);
+
+sub get
+{
+	my ($self,$item) = @_;
+	if(exists $_information{$item})
+	{
+		return $_information{$item}
+	}
+	else
+	{
+		die "'$item' is an unrecognised locale item"
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/jp.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/jp.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/jp.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,35 @@
+package WebAppFramework::Locale::jp;
+use strict;
+use base 'WebAppFramework::Locale';
+use vars '%_information';
+
+%_information = (
+	# text translations
+	'Day' => '日',
+	'Month' => '月',
+	'Year' => '年',
+	
+	# Note: Preferred coding style for accessing lists of months etc is to look them up
+	# using the C++ object at runtime.
+	'MonthList' => '1月|2月|3月|4月|5月|6月|7月|8月|9月|10月|11月|12月',
+	'DayList' => '月曜日|火曜日|水曜日|木曜日|金曜日|土曜日|日曜日',
+
+	# other information
+	'FormItemDateOrdering' => 'YMD',	# how fields in a date form item are ordered
+);
+
+sub get
+{
+	my ($self,$item) = @_;
+	if(exists $_information{$item})
+	{
+		return $_information{$item}
+	}
+	else
+	{
+		die "'$item' is an unrecognised locale item"
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/nl.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/nl.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale/nl.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,37 @@
+package WebAppFramework::Locale::nl;
+use strict;
+use base 'WebAppFramework::Locale';
+use vars '%_information';
+
+%_information = (
+	# text translations
+	'Day' => 'dag',
+	'Month' => 'maand',
+	'Year' => 'jaar',
+	
+ 	# NOTE: Dutch people do not capitalise their month/day names
+	
+	# Note: Preferred coding style for accessing lists of months etc is to look them up
+	# using the C++ object at runtime.
+	'MonthList' => 'januari|februari|maart|april|mei|juni|juli|augustus|september|oktober|november|december',
+	'DayList' => 'maandag|dinsdag|woensdag|donderdag|vrijdag|zaterdag|zondag',
+	
+	# other information
+	'FormItemDateOrdering' => 'DMY',	# how fields in a date form item are ordered
+);
+
+sub get
+{
+	my ($self,$item) = @_;
+	if(exists $_information{$item})
+	{
+		return $_information{$item}
+	}
+	else
+	{
+		die "'$item' is an unrecognised locale item"
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Locale.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,44 @@
+package WebAppFramework::Locale;
+use strict;
+
+# Dervied classes must implement the 'get' subroutine, which
+# returns the 'translated' string. This can either be text, or
+# other information such as orderings.
+#
+# Note that some things are implemented by the Locale C++ object
+# at runtime.
+
+
+# Constructor
+sub new
+{
+	my ($type, %params) = @_;
+	my $self = {};
+	bless $self, $type;
+}
+
+# Returns name of the local. By default, just returns the
+# last element of the module type.
+sub get_name
+{
+	my ($self) = @_;
+	my $type = ref($self);
+	$type =~ m/::([^:]+?)\Z/;
+	$1
+}
+
+# Returns the C++ class name
+sub get_cpp_classname
+{
+	my ($self) = @_;
+	return 'WAFLocale_'.($self->get_name())
+}
+
+# Returns the C++ header file which needs to be included
+sub get_cpp_include_name
+{
+	my ($self) = @_;
+	return ($self->get_cpp_classname()).'.h'
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Output.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Output.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Output.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,293 @@
+package WebAppFramework::Output;
+use strict;
+use WebAppFramework::Unit;
+use WebAppFramework::LanguageFile;
+use vars qw/$_last_output_object_created/;
+
+sub new
+{
+	my ($type, $filehandle, $page_identifier) = @_;
+	my $self = {};
+	$$self{'filehandle'} = $filehandle;
+	$$self{'text_var'} = '_WebAppFrameworkText_'.$page_identifier;
+	$$self{'indent_level'} = 0;
+	bless $self;
+	# store so the global find_output function can find it...
+	$_last_output_object_created = $self;
+	$self
+}
+
+# This function returns the last Output object created.
+# Use as an absolute last resort.
+sub find_output_object
+{
+	return $_last_output_object_created;
+}
+
+sub DESTROY
+{
+	my ($self) = @_;
+
+	# make sure that if the language files are not saved, there is at least a big warning printed
+	if(exists $$self{'language_file'} || exists $$self{'default_language_file'})
+	{
+		print "\n\nWARNING: Output object destroyed without saving updated language files\n\n\n"
+	}
+}
+
+sub setup_languages
+{
+	my ($self, $language, $default_language, $current_page) = @_;
+	$$self{'language'} = $language;
+	$$self{'default_language'} = $default_language;
+	
+	if($language eq 'COMMON')
+	{
+		# no translation for the COMMOM file
+		return;
+	}
+	
+	# load the default language
+	my $lang = undef;
+	my $default_lang = undef;
+	
+	if(!-e 'Languages/default.txt')
+	{
+		print "WARNING: Creating empty language file for default language\n";
+		$lang = WebAppFramework::LanguageFile->new('Languages/default.txt', $current_page, undef);
+	}
+	else
+	{
+		# if the language is being loaded to be used as the default language, don't
+		# suppress the usage for the current page, otherwise the default language
+		# loses all it's usage lists.
+		$lang = WebAppFramework::LanguageFile->load('Languages/default.txt', $current_page, undef, ($language ne $default_language));
+	}
+
+	# load the language file
+	if($language ne $default_language)
+	{
+		$default_lang = $lang;
+		$lang = undef;
+		
+		my $lang_file = 'Languages/'.$language.'.txt';
+		if(!-e $lang_file)
+		{
+			print "WARNING: Creating empty language file for language $language ($lang_file)\n";
+			$lang = WebAppFramework::LanguageFile->new($lang_file, $current_page, $default_lang);
+		}
+		else
+		{
+			$lang = WebAppFramework::LanguageFile->load($lang_file, $current_page, $default_lang);
+		}
+	}
+	
+	# Store
+	$$self{'language_file'} = $lang;
+	$$self{'default_language_file'} = $default_lang if defined $default_lang;
+}
+
+sub get_language
+{
+	my ($self) = @_;
+	return $$self{'language'};
+}
+
+sub get_default_language
+{
+	my ($self) = @_;
+	return $$self{'default_language'};
+}
+
+# Register a code processor. This object will be able to modify any code
+# which is written using write_code(). It must implement preprocess_code()
+# which takes a two arguments, the first of this output object, and the
+# second of the code to preprocess, and returns the code to write.
+sub set_code_preprocessor
+{
+	my ($self,$preprocess) = @_;
+	$$self{'preprocess_code'} = $preprocess;
+}
+
+sub write_code
+{
+	my ($self, $code) = @_;
+
+	$self->flush_text();
+
+	my $f = $$self{'filehandle'};
+	
+	# pre-process code?
+	if(exists $$self{'preprocess_code'})
+	{
+		$code = $$self{'preprocess_code'}->preprocess_code($code, $self);
+	}
+	
+	# split up and do auto-indenting
+	my @lines = split /(\n+)/,$code;
+	for my $l (@lines)
+	{
+		$l =~ s/\A\t+//;
+		if($l =~ m/[^\n]/s)
+		{
+			# something other than just blank lines is present
+			my $this_line_indent = $$self{'indent_level'};
+			# do level handling
+			my $t = $l;
+			# remove strings and comments
+			$t =~ s`\\(\\|n|t|")`*`;
+			$t =~ s`//.*\Z``;
+			$t =~ s`"[^"]"``;
+			my $balanced = 0;
+			while($t =~ m/({|})/g)
+			{
+				if($1 eq '{')
+				{
+					$$self{'indent_level'} ++;
+					$balanced ++;
+				}
+				else
+				{
+					$$self{'indent_level'} --;
+					$balanced --;
+				}
+			}
+			# fudge for case and default labels
+			$this_line_indent -- if $balanced < 0;
+			$this_line_indent -- if $l =~ m/\A\s*(case|default|public|private|protected)/;
+			# write out the line at an appropraite indent
+			print $f "\t" x $this_line_indent, $l;
+		}
+		else
+		{
+			print $f $l
+		}
+	}
+	
+#	print $f $code;
+}
+
+sub write_text
+{
+	my ($self, $text) = @_;
+
+	# get rid of bad characters
+	$text =~ tr/\r//d;
+	
+	$$self{'text'} .= $text;
+}
+
+sub write_text_translated
+{
+	my ($self, $text) = @_;
+
+	# check
+	die "In Output, setup_languages not called, or attempting to use translation in COMMON files"
+		unless exists $$self{'language_file'};
+
+	# route via language
+	$self->write_text($$self{'language_file'}->translate_and_update($text));
+}
+
+sub write_text_translate_within_markers
+{
+	my ($self, $text) = @_;
+
+	# translate things within the HTML markers
+	$self->translate_within_markers(\$text);
+	
+	# write the text
+	$self->write_text($text);
+}
+
+# note: first arg is a reference
+sub translate_within_markers
+{
+	my ($self, $text_r) = @_;
+	die "Output::translate_within_markers must be passed a reference to alter" unless ref($text_r);
+	
+	# use a search and replace to adjust the text
+	$$text_r =~ s/\Q<!--T-->\E(.*?)\Q<!--T-->\E/$$self{'language_file'}->translate_and_update($1)/ges;
+}
+
+sub translate_text
+{
+	my ($self, $text) = @_;
+	
+	# check
+	die "In Output, setup_languages not called, or attempting to use translation in COMMON files"
+		unless exists $$self{'language_file'};
+
+	# route via language
+	return $$self{'language_file'}->translate_and_update($text);
+}
+
+sub flush_text
+{
+	my ($self) = @_;
+	
+	my $text = $$self{'text'};
+	my $text_len = length($text);
+	
+	if($text ne '')
+	{
+		# the text
+		my $f = $$self{'filehandle'};
+		print $f "\t" x $$self{'indent_level'},"rResponse.Write(",string_to_cpp_static_string($text, $$self{'indent_level'} + 2),", $text_len);\n";
+		
+		# unset the stored text
+		$$self{'text'} = '';
+	}
+}
+
+sub string_to_cpp_static_string
+{
+	my ($string, $indent_level) = @_;
+	$indent_level = 2 if $indent_level eq '';
+
+	# not necessarily the most wonderful way of doing this, but...
+	my @lines = split /(.{0,80})/s, $string;
+	my $str = '';
+
+	my $first_line = 1;
+	for(my $l = 0; $l <= $#lines; $l++)
+	{
+		next if $lines[$l] eq '';
+		
+		# line start
+		$str .= (($first_line)?'"':" \\\n".("\t" x $indent_level).'"');
+		$first_line = 0;
+
+		# Alter partial line to fit in a nice C string
+		# WARNING: Must not change the size of the string by these transforms!
+		my $t = $lines[$l];
+		$t =~ s/\\/\\\\/g;
+		$t =~ s/\n/\\n/g;
+		$t =~ s/\t/\\t/g;
+		$t =~ s/"/\\"/g;
+
+		# output	
+		$str .= $t.'"';	
+	}
+
+	$str
+}
+
+sub save_language_files
+{
+	my ($self) = @_;
+
+	if(exists $$self{'language_file'})
+	{
+		$$self{'language_file'}->save();
+		delete $$self{'language_file'};
+	}
+	if(exists $$self{'default_language_file'})
+	{
+		$$self{'default_language_file'}->save();
+		delete $$self{'default_language_file'};
+	}
+}
+
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookup.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookup.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookup.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,52 @@
+package WebAppFramework::Unit::ChoiceLookup;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit to display an element from a translated list using a page variable as the index.
+
+# new() parameters:
+#	Variable => name of variable
+#	Choices => list for translation, | separated
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		my $var = $self->get_variable($$self{'Variable'});
+		$output->write_code("{\nint32_t value = ".$var->convert_to('int32_t').";\n");
+		$self->write_choice_display($output, 'value');
+		$output->write_code("}\n");
+	}
+}
+
+# writes the code to display the actual list item
+sub write_choice_display
+{
+	my ($self, $output, $localvar) = @_;
+	
+	# translate the list of choices and split into elements
+	my $choicelist = $output->translate_text($$self{'Choices'});
+	my @choices = split /\|/,$choicelist;
+	
+	# write code...
+	my $list_len = ($#choices + 1);
+	$output->write_code("static const char *lookup[] = {\n");
+	$output->write_code(join(', ',map {WebAppFramework::Output::string_to_cpp_static_string($_)} @choices)."\n");
+	$output->write_code("};\n");
+	$output->write_code(<<__E);
+			if($localvar < 0 || $localvar >= $list_len)
+			{
+				// Bad value
+				rResponse.Write("?",1);
+			}
+			else
+			{
+				rResponse.WriteString(lookup[value]);
+			}
+__E
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookupList.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookupList.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ChoiceLookupList.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,65 @@
+package WebAppFramework::Unit::ChoiceLookupList;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::ChoiceLookup';
+
+# Unit to display a list of elements from a translated list using a page variable as the index.
+
+# new() parameters:
+#	Variable => name of variable (resolving to a string, containing a ',' separated list of integers)
+#	Choices => list for translation, | separated
+#	Separator => separator (optional, not translated)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# separator
+		my ($separator1,$separator2) = ('','');
+		if(exists $$self{'Separator'})
+		{
+			my $separator = $$self{'Separator'};
+			$separator1 = "\nbool first = true;";
+			$separator2 = "\nif(first)\n{\nfirst = false;\n}\nelse\n{\nrResponse.Write(".
+					WebAppFramework::Output::string_to_cpp_static_string($separator)
+					.', '.(length($separator)).");\n}";
+		}
+	
+		# get variable, and make available to the code.
+		my $var = $self->get_variable($$self{'Variable'});
+		$output->write_code("{\nstd::string _CLL_list(".$var->convert_to('std::string').");\n");
+		$output->write_code(<<__E);
+			const char *ptr = _CLL_list.c_str();
+			char *endptr = 0;
+			int32_t value = 0;$separator1
+			while((value = ::strtol(ptr, &endptr, 0)), endptr != 0 && endptr != ptr)
+			{$separator2
+__E
+		$self->write_choice_display($output, 'value');
+		$output->write_code(<<__E);
+				// end of list?
+				if(*endptr != ',') break;
+				// next
+				ptr = endptr + 1;
+			}
+		}
+__E
+	}
+}
+
+# need to include the library functions
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_SYSTEM)
+	{
+		return ('stdlib.h','limits.h')
+	}
+	return ()
+}
+
+
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Code.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Code.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Code.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,82 @@
+package WebAppFramework::Unit::Code;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit to output C++ code
+
+# new() parameters:
+#	Code => raw HTML to output
+#	Phase => name of phase or array of names of phases
+#			(default to PHASE_LANG_CPP_HANDLE_OUTPUT)
+#	Headers => List of header files to include on relevant page.
+#	SystemHeaders => List of system headers to include.
+
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# get list of phases this applies to
+	my @phases = $self->list_to_array($$self{'Phase'});
+	if($#phases < 0)
+	{
+		push @phases,'lang_output'
+	}
+
+	# output the code
+	for(@phases)
+	{
+		if($phase == $self->phase_name_to_number($_))
+		{
+			# write the code
+			$output->write_code($$self{'Code'});
+		}
+	}
+}
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+
+	return () unless exists $$self{'Headers'} || exists $$self{'SystemHeaders'};
+
+	# Work out which of the files to output the headers in.
+	# Remember that there's a heirarchy with each one included
+	# in files lower down.
+
+	my $h_sys = WebAppFramework::Unit::HEADERS_SYSTEM;
+	my $h_pro = WebAppFramework::Unit::HEADERS_PROJECT;
+	
+	for($self->list_to_array($$self{'Phase'}))
+	{
+		if(m/\Amain/)
+		{
+			if($h_sys != WebAppFramework::Unit::HEADERS_GLOBAL_H_SYSTEM)
+			{
+				# Only if not already promoted to global
+				$h_sys = WebAppFramework::Unit::HEADERS_PAGE_H_SYSTEM;
+				$h_pro = WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT;
+			}
+		}
+		elsif(m/\Aglobal/)
+		{
+			$h_sys = WebAppFramework::Unit::HEADERS_GLOBAL_H_SYSTEM;
+			$h_pro = WebAppFramework::Unit::HEADERS_GLOBAL_H_PROJECT;
+		}
+	}
+
+	# output headers?
+	if($type == $h_sys && exists $$self{'SystemHeaders'})
+	{
+		return $self->list_to_array($$self{'SystemHeaders'});
+	}
+	if($type == $h_pro && exists $$self{'Headers'})
+	{
+		return $self->list_to_array($$self{'Headers'});
+	}
+	return ()
+}
+
+
+1;
\ No newline at end of file

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/CppSTLContainer.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/CppSTLContainer.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/CppSTLContainer.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,105 @@
+package WebAppFramework::Unit::DataSource::CppSTLContainer;
+use strict;
+use base 'WebAppFramework::Unit::DataSource';
+use CppVariable;
+
+# Bind to data in an STL container.
+#
+# Parameters:
+# 	Container => page variable containing typed container variable
+#	IgnoreBlank => if set, blank strings are ignored
+
+# see base class
+sub write_bound_item
+{
+	my ($self, $function, $output, $phase, $subphase) = @_;
+	
+	# ensure we know what we're going
+	$self->setup();
+	my $var = $self->get_variable($$self{'Container'});
+	my $v_type = $var->type();
+	my $v_name = $var->name();
+	
+	# for writing the item
+	my $key;
+	my $string;
+
+	# write the loop
+	if($$self{'_type'} eq 'vector')
+	{
+		$output->write_code(<<__E);
+			{
+				int32_t i = 0;
+				for(i = 0; i < (int32_t)$v_name.size(); ++i)
+				{
+__E
+		if(exists $$self{'IgnoreBlank'})
+		{
+			$output->write_code(<<__E);
+					if(${v_name}[i].empty()) continue;
+__E
+		}
+		$key = 'i';
+		$string = cppvar($$self{'_string_type'}, $v_name.'[i]')->convert_to('std::string');
+	}
+	elsif($$self{'_type'} eq 'map')
+	{
+		$output->write_code(<<__E);
+			{
+				for(${v_type}::const_iterator i($v_name.begin()); i != $v_name.end(); ++i)
+				{
+__E
+		$key = 'i->first';
+		$string = cppvar($$self{'_string_type'}, 'i->second')->convert_to('std::string');
+	}
+	
+	# write whatever's required to output the item
+	&$function($key, $string, $output, $phase, $subphase);
+
+	$output->write_code(<<__E);
+			}
+		}
+__E
+}
+
+
+
+# see base class
+sub key_is_integer
+{
+	my ($self) = @_;
+	$self->setup();
+	return $$self{'_key_is_int'};
+}
+
+# get all the necessary bits of information
+sub setup
+{
+	my ($self) = @_;
+	return if exists $$self{'_type'};
+	
+	my $var = $self->get_variable($$self{'Container'});
+	my $type = $var->type();
+	
+	# what kind of type is it?
+	if($type =~ m/vector\s*<\s*([^>]+)\s*>/)
+	{
+		$$self{'_type'} = 'vector';
+		$$self{'_key_is_int'} = 'int';
+		$$self{'_string_type'} = $1;
+	}
+	elsif($type =~ m/map\s*<\s*([^,]+)\s*,\s*([^>]+)\s*>/)
+	{
+		$$self{'_type'} = 'map';
+		$$self{'_key_is_int'} = ($1 =~ m/int/);
+		$$self{'_key_type'} = $1;
+		$$self{'_string_type'} = $2;
+	}
+	else
+	{
+		die "Unknown container type $type for DataSource::CppSTLContainer\n";
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/DatabaseQuery.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/DatabaseQuery.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/DatabaseQuery.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,116 @@
+package WebAppFramework::Unit::DataSource::DatabaseQuery;
+use strict;
+use base 'WebAppFramework::Unit::DataSource';
+use Database::Query;
+use CppVariable;
+
+# Bind to data from a database query.
+#
+# Parameters:
+# 	Query => database query to use, or an anonymous hash of the Query parameters.
+#	Args => Arguments for query
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# ensure the query object is created
+	$self->ensure_query();
+	
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# Create an adaptor object
+		my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+			'Target' => $$self{'Query'},
+			'Source' => $self,
+			'Args' => $$self{'Args'});
+		# and store
+		$$self{'_adaptor'} = $adaptor;
+		
+		# register the namespace
+		$self->register_variable_namespace($$self{'Name'}, [$$self{'Query'}->get_results()]);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+	{
+		$output->write_code($$self{'Query'}->generate_h())
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_CODE)
+	{
+		$output->write_code($$self{'Query'}->generate_cpp())
+	}
+}
+
+
+# see base class
+sub write_bound_item
+{
+	my ($self, $function, $output, $phase, $subphase) = @_;
+	
+	# ensure the query object is created
+	$self->ensure_query();
+	my $query = $$self{'Query'};
+	my $typename = $query->get_name();
+
+	my $adaptor = $$self{'_adaptor'};
+	my $execute = $adaptor->generate_call('query.Execute', '');
+	
+	my @results = $query->get_results();
+	die "For DataSource::DatabaseQuery, query must have exactly two results" unless $#results == 1;
+	my $key = $results[0];
+	my $string = $results[1];
+	
+	# write loop code
+	$output->write_code(<<__E);
+		{
+			$typename query(mApplication.GetDatabaseConnection());
+			$execute
+			while(query.Next())
+			{
+__E
+		&$function('query.Get'.$key->name().'()',
+				cppvar($string->type(), 'query.Get'.$string->name().'()')->convert_to('std::string'),
+				$output, $phase, $subphase);
+	$output->write_code(<<__E);
+			}
+		}
+__E
+}
+
+
+# see base class
+sub key_is_integer
+{
+	my ($self) = @_;
+	# check the type of the key...
+	$self->ensure_query();
+	my $query = $$self{'Query'};
+	my @results = $query->get_results();
+	return $results[0]->is_int_type();
+}
+
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+	{
+		return ('DatabaseQuery.h')
+	}
+	return ()
+}
+
+
+sub ensure_query()
+{
+	my ($self) = @_;
+	if(ref($$self{'Query'}) ne 'Database::Query')
+	{
+		# create a database query object as the given data isn't such an object
+		my %p = %{$$self{'Query'}};
+		my $q = Database::Query->new(%p);
+		$$self{'Query'} = $q;
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/Null.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/Null.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/Null.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,10 @@
+package WebAppFramework::Unit::DataSource::Null;
+use strict;
+use base 'WebAppFramework::Unit::DataSource';
+
+# Null binding -- shows no data
+
+# Nothing needs to be overridden from the base class
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/StaticStrings.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/StaticStrings.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource/StaticStrings.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,117 @@
+package WebAppFramework::Unit::DataSource::StaticStrings;
+use strict;
+use base 'WebAppFramework::Unit::DataSource';
+use vars '$_strings_count';
+use WebAppFramework::Output;
+
+# Bind to a static list of strings (strings will be embedded as constants).
+
+# Parameters to new()
+#	Strings => ref to array of strings (NOT translated)
+#				 or a | separated list of strings (which WILL be translated)
+
+# This mainly used by the WebAppFramework::Unit::FormItem::Choice unit to
+# write out large numbers of choices more efficiently.
+
+$_strings_count = 0;
+
+sub new_postcreate
+{
+	my ($self) = @_;
+	
+	# make a name for the static strings variable which shouldn't collide with anything.
+	$$self{'_varname'} = '_ds_StaticStrings_'.$_strings_count;
+	++$_strings_count;
+}
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	# write out the strings in the declaraction phase, so there's only ever
+	# one copy, regardless of how many times the thing is output. (for example, in forms)
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_DECLARATION)
+	{
+		my $varname = $$self{'_varname'};
+	
+		my $strings_r = $$self{'Strings'};
+		unless(ref($strings_r))
+		{
+			# for checking that the number of items is the same
+			my @untranslated = split /\|/,$strings_r;
+	
+			# unless we've already got translated strings, translate and split...
+			$strings_r = [split /\|/,($output->translate_text($strings_r))];
+			
+			# check number returned
+			if($#untranslated != $#$strings_r)
+			{
+				die "In StaticStrings data source, number of translated strings does not match number of untranslated strings"
+			}
+		}
+		
+		# store max integer value for later
+		$$self{'_max_int_value'} = $#$strings_r + 1;
+	
+		# setup various bits
+		my $num_strings = $#$strings_r + 1;
+	
+		# Write strings
+		$output->write_code("#define ${varname}__COUNT $num_strings\nstatic const char *${varname}[] = {\n");
+		my $sep = '';
+		my $list = '';
+		for(@$strings_r)
+		{
+			$list .= $sep . WebAppFramework::Output::string_to_cpp_static_string($_);
+			$sep = ",\n"
+		}
+		$output->write_code($list."\n};\n");
+	}
+}
+
+# see base class
+sub write_bound_item
+{
+	my ($self, $function, $output, $phase, $subphase) = @_;
+	
+	# write for loop
+	my $varname = $$self{'_varname'};
+	$output->write_code(<<__E);
+		for(int32_t key = 0; key < ${varname}__COUNT; ++key)
+		{
+__E
+		&$function('key', $varname.'[key]', $output, $phase, $subphase);
+	$output->write_code(<<__E);
+		}
+__E
+}
+
+
+# see base class
+sub get_integer_range
+{
+	my ($self) = @_;
+	my $max_int;
+	my $strings_r = $$self{'Strings'};
+	if(ref($strings_r))
+	{
+		$max_int = $#$strings_r;
+	}
+	else
+	{
+		my @c = split /\|/,$strings_r;
+		$max_int = $#c;
+	}
+	return (0,$max_int)
+}
+
+# see base class
+sub string_is_trusted
+{
+	# since the strings are static, they are trusted.
+	return 1;
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/DataSource.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,37 @@
+package WebAppFramework::Unit::DataSource;
+use strict;
+use base 'WebAppFramework::Unit';
+
+
+# Is the key (ie value="" attribute) an integer? 
+sub key_is_integer
+{
+	# default is an integer, it's nicer to store
+	return 1;
+}
+
+# Is the string data trusted to be free of nasty things?
+sub string_is_trusted
+{
+	# default is not to trust
+	return 0;
+}
+
+# Write the code
+# Args: ref to function to write an output, output, phase, subphase
+#			function takes args: key_source, string_souce, output, phase, subphase
+sub write_bound_item
+{
+	my ($self, $function, $output, $phase, $subphase) = @_;
+	
+	# default does nothing
+}
+
+# Get the maximum possible range of an integer result.
+# Return (min,max) as an array, either undefined to omit the test for that bound
+sub get_integer_range
+{
+	return ()
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Authenticate.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Authenticate.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Authenticate.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,411 @@
+package WebAppFramework::Unit::Database::Authenticate;
+use strict;
+use base 'WebAppFramework::Unit';
+use Database::Query;
+use CppVariable;
+use WebAppFramework::Unit::Code;
+
+# new() parameters:
+#	Name => Name of the object (will be registered as a namespace)
+#	Query => Database::Query object which should be executed, or ref to hash array for constructor (which can omit 'Name')
+#			First parameter must be the ID, second optional must be token
+#	TokenColumn => Name of the column (in Results of the Query) which contains the token
+#	CredentialsSource => Name of the variable containing the credentials (cookie, form param?)
+#	RedirectToOnAuthFailure => Page to redirect the user to if there's no row available (optional)
+#	DisableRedirectOnPages => List of pages to not do the above redirect
+#	TokenFilter => 'MD5' to implement MD5 hashing (optional)
+#	MD5SecretConfigVar => If MD5 token filter, the source of the secret for the filter
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# check this is in the page template, not just in a page
+	die "Database::Authenticate must be added in the setup_page subroutine only."
+		unless exists $$self{'_in_default_page'};
+
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		for(qw/Name Query TokenColumn CredentialsSource/)
+		{
+			die "$_ not set for Database::Authenticate" unless exists $$self{$_}
+		}
+	
+		# make sure there's a query object created
+		$self->ensure_query();
+		
+		# register the namespace
+		$self->register_variable_namespace($$self{'Name'},
+			[$$self{'Query'}->get_results(), cppvar('bool IsAuthenticated'), cppvar('std::string Credentials')]);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_GLOBAL_H_DECLARATION)
+	{
+		# write the query object
+		$output->write_code($$self{'Query'}->generate_h());
+		
+		# extras...
+		my ($constructor_extra_args, $extra_vars);
+		if($$self{'TokenFilter'} eq 'MD5')
+		{
+			$constructor_extra_args = ', WebApplication &rApplication';
+			$extra_vars = "\n\t\t\t\tstatic std::string msTokenFilterSecret;\n\t\t\t\tvoid FilterToken(const std::string &rIn, std::string &rOut);";			
+		}
+		
+		# write the derived class
+		my $classname = $$self{'Name'}.'Impl';
+		my $basename = $$self{'Query'}->get_name();
+		$output->write_code(<<__E);
+			class WebApplication;	// for basic access
+			class ${classname} : public ${basename}
+			{
+			public:
+				${classname}(DatabaseConnection &rConnection$constructor_extra_args);
+				~${classname}();
+			private:
+				// no copying
+				${classname}(const ${classname} &);
+				${classname} operator=(const ${classname} &);
+			public:
+				// hide base class functions
+				void Execute();
+				
+				// Implement extra functions
+				std::string MakeCredentials(const std::string &ID, const std::string &Password);
+				void Authenticate(const std::string &rCredentials);
+
+				// Find state
+				bool IsAuthenticated() const {return mCredentials.size() > 0;}
+				const std::string &GetCredentials() const {return mCredentials;}
+				// For interfacing to namespace, so can use page var Name.IsAuthenticated
+				bool GetIsAuthenticated() const {return IsAuthenticated();}
+				
+				// Unset authentication state
+				void SetToUnauthenticated();
+			
+			private:
+				std::string mCredentials;$extra_vars
+			};
+__E
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_GLOBAL_CPP_CODE)
+	{
+		# write the query object
+		$output->write_code($$self{'Query'}->generate_cpp());
+		
+		# write the derviced class
+		my $tokencolumn = $$self{'TokenColumn'};
+		my $classname = $$self{'Name'}.'Impl';
+		my $basename = $$self{'Query'}->get_name();
+		# get parameters (arguments of Execute function)
+		my @args = $$self{'Query'}->get_arguments();
+		# does execute need an addition argument?
+		my $execute_extra = '';
+		if($#args == 1)
+		{
+			$execute_extra = ', token';
+		}
+		elsif($#args > 1)
+		{
+			die "Query for Database::Authenticate has too many paramters";
+		}
+
+		# extras...
+		my ($constructor_extra_args, $constructor);
+		if($$self{'TokenFilter'} eq 'MD5')
+		{
+			$constructor_extra_args = ', WebApplication &rApplication';
+			my $config_var = $$self{'MD5SecretConfigVar'};
+			$constructor = <<__E;
+				if(msTokenFilterSecret.empty())
+				{
+					// Load secret from the configuration file -- should only be done once
+					const Configuration &rconfig(rApplication.GetConfiguration());
+					msTokenFilterSecret = rconfig.GetKeyValue("$config_var");
+				}
+
+__E
+		}
+		
+		# write the actual implementations
+		$output->write_code(<<__E);
+			${classname}::${classname}(DatabaseConnection &rConnection$constructor_extra_args)
+				: ${basename}(rConnection)
+			{
+			$constructor}
+			${classname}::~${classname}()
+			{
+			}
+			void ${classname}::Authenticate(const std::string &rCredentials)
+			{
+				// Un-authenticate
+				mCredentials.erase();
+			
+				// Split into id and token
+				std::string::size_type separator = rCredentials.find('~');
+				if(separator != std::string::npos)
+				{
+					// Split into the two strings
+					std::string id(rCredentials.substr(0, separator));
+					std::string token(rCredentials.substr(separator + 1));
+				
+					// Run the query
+					${basename}::Execute(id$execute_extra);
+				
+					// Got a result (and one only?)
+					if(GetNumberRows() == 1 && Next())
+					{
+						// Check against token
+__E
+		if($$self{'TokenFilter'} eq 'MD5')
+		{
+			$output->write_code(<<__E);
+						std::string generatedToken;
+						FilterToken(Get$tokencolumn(), generatedToken);
+						if(generatedToken == token)
+__E
+		}
+		else
+		{
+			$output->write_code(<<__E);
+						if(Get$tokencolumn() == token)
+__E
+		}
+		$output->write_code(<<__E);
+						{
+							// Looks good!
+							mCredentials = rCredentials;
+						}
+					}
+				}		
+			}
+			void ${classname}::SetToUnauthenticated()
+			{
+				if(IsAuthenticated())
+				{
+					// There is some data ready for retrieval, get rid of it
+					// by moving onto the next row, which shouldn't exist because
+					// we only allow things to be authenticated if there is 1 row.
+					Next();
+				}
+				
+				// Unset the credentials, to mark as not authenticated
+				mCredentials.erase();
+			}
+__E
+
+		if($$self{'TokenFilter'} eq 'MD5')
+		{
+			# write the filter code function
+			$output->write_code(<<__E);
+			std::string ${classname}::MakeCredentials(const std::string &ID, const std::string &Password)
+			{
+				std::string t;
+				FilterToken(Password, t);
+				return ID + '~' + t;
+			}
+			void ${classname}::FilterToken(const std::string &rIn, std::string &rOut)
+			{
+				MD5Digest digest;
+				digest.Add(msTokenFilterSecret);
+				digest.Add(rIn);
+				digest.Add(msTokenFilterSecret);
+				digest.Finish();
+				rOut = digest.DigestAsString();
+			}
+			std::string ${classname}::msTokenFilterSecret;
+__E
+		}
+		else
+		{
+			$output->write_code(<<__E);
+			std::string ${classname}::MakeCredentials(const std::string &ID, const std::string &Password)
+			{
+				return ID + '~' + Password;
+			}
+__E
+		}
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_VARS)
+	{
+		my $extra = ($$self{'TokenFilter'} eq 'MD5')?', *this':'';
+		$output->write_code($$self{'Name'}.'Impl '.$$self{'Name'}."(mApplication.GetDatabaseConnection()$extra);\n");
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_SECURITY)
+	{
+		# Get credentials, try to authenticate
+		$output->write_code($$self{'Name'}.'.Authenticate('.
+			($self->get_variable($$self{'CredentialsSource'})->convert_to('std::string'))
+			.");\n");
+		# redirect?
+		my @disable_on_pages;
+		if(exists $$self{'DisableRedirectOnPages'})
+		{
+			@disable_on_pages = $self->list_to_array($$self{'DisableRedirectOnPages'});
+		}
+		my $disable = 0;
+		for(@disable_on_pages) {$disable = 1 if $_ eq $self->get_pagename()}
+		if(exists $$self{'RedirectToOnAuthFailure'} && !$disable)
+		{
+			$output->write_code('if(!'.$$self{'Name'}.".IsAuthenticated())\n{\n");
+			$output->write_code($self->make_redirect_code(@{$$self{'RedirectToOnAuthFailure'}}));
+			# will output a return statement
+			$output->write_code("\n}\n");
+		}
+	}
+}
+
+sub ensure_query()
+{
+	my ($self) = @_;
+	if(ref($$self{'Query'}) ne 'Database::Query')
+	{
+		# create a database query object as the given data isn't such an object
+		my %p = %{$$self{'Query'}};
+		$p{'Name'} = $$self{'Name'}.'Base' unless exists $p{'Name'};
+		my $q = Database::Query->new(%p);
+		$$self{'Query'} = $q;
+	}
+}
+
+# write the code for the validate function
+sub generate_validate_code
+{
+	my ($self, $id_field, $token_field) = @_;
+
+	# make sure query is set up correctly
+	$self->ensure_query();
+
+	# arguments for the Execute query built
+	my @args = $$self{'Query'}->get_arguments();
+	my $execute_args = 'm'.$id_field;
+	$execute_args .= ', m'.$token_field if $#args > 0;
+	
+	# get other data
+	my $token_column = $$self{'TokenColumn'};
+	my $auth_name = $$self{'Name'};
+
+	# return generated code
+	return <<__E;
+		${auth_name}Base query(rApplication.GetDatabaseConnection());
+		query.Execute($execute_args);
+		if(query.Next())
+		{
+			if(query.Get${token_column}() == m$token_field)
+			{
+				m${token_field}ValidityError = WebAppForm::Valid;
+			}
+		}
+__E
+}
+
+# set up a form to validate the password
+sub set_validate_function_on_form
+{
+	my ($self, $webapp, $form, $id_field, $token_field) = @_;
+	
+	# check a few things on the form
+	my $form_args_to_validate = ($form->param_exists('ArgsToValidate'))?($form->get('ArgsToValidate')):'';
+	die "In Database::Authenticate, set_validate_function_on_form, form doesn't have parameter 'ArgsToValidate' => 'Application'"
+		unless $form_args_to_validate eq 'Application';
+	my $form_validation = ($form->param_exists('FormValidation'))?($form->get('FormValidation')):'';
+	die "In Database::Authenticate, set_validate_function_on_form, form doesn't have parameter 'FormValidation' => 'simple'"
+		unless $form_validation eq 'simple';
+
+	# collect extra names for making the validate function
+	my $wan = $webapp->get_webapp_name();
+	my $dcn = $wan.'Form'.ucfirst($form->get('FormName'));
+	
+	# Now write a suitable function
+	my $fn = <<__E;
+	void ${dcn}::Validate($wan &rApplication)
+	{
+		// Autogenerated by Database::Authenticate unit
+__E
+	$fn .= $self->generate_validate_code($id_field, $token_field);
+	$fn .= "\t}\n";
+	
+	# add it to the tree
+	$form->add_post_unit(WebAppFramework::Unit::Code->new('Phase' => 'main_code', 'Code' => $fn));
+}
+
+
+sub set_HandleSubmission_on_form
+{
+	my ($self, $form, $id_field, $token_field, @link_spec) = @_;
+
+	# data required
+	my $formname = $form->get('FormName');
+	my $auth_name = $$self{'Name'};
+
+	# how should the credentials be set?
+	my $credentialsSource = $$self{'CredentialsSource'};
+	my $set_credentials_line;
+	if($credentialsSource =~ m/\Acookie\.(.+?)\Z/)
+	{
+		# simply set the cookie!
+		$set_credentials_line = qq!rResponse.SetCookie("$1", credentials.c_str());\n!;
+	}
+	elsif($credentialsSource =~ m/\Aparams\.(.+?)\Z/)
+	{
+		# slightly more difficult here, need to modify the link
+		my @addition = ($1,'LOCAL:std::string credentials');
+		# is it a normal list, or an anon array?
+		if(ref($link_spec[0]))
+		{
+			push @{$link_spec[0]},@addition
+		}
+		else
+		{
+			push @link_spec,@addition
+		}
+	}
+	else
+	{
+		die "Database::Authenticate unit can't generate HandleSubmission code for CredentialsSource $credentialsSource"
+	}
+
+	# build link specification
+	my $link_spec_text = $self->link_spec_to_WAF_code(@link_spec);
+	
+	# write the code
+	$form->set('HandleSubmission' => <<__E);
+		// Set the credentials
+		std::string credentials(${auth_name}.MakeCredentials(${formname}.Get$id_field(), ${formname}.Get$token_field()));
+		$set_credentials_line
+		// Set redirect
+		std::string uri($link_spec_text);
+		rResponse.SetAsRedirect(uri.c_str());
+
+		return true;
+__E
+}
+
+
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_GLOBAL_H_PROJECT)
+	{
+		if($$self{'TokenFilter'} eq 'MD5')
+		{
+			return ('DatabaseQuery.h', 'MD5Digest.h', 'Configuration.h', 'WebApplication.h')
+		}
+		else
+		{
+			return ('DatabaseQuery.h')
+		}
+	}
+	return ()
+}
+
+sub in_default_page()
+{
+	my ($self) = @_;
+	
+	$$self{'_in_default_page'} = 1;
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/DisplayQuery.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/DisplayQuery.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/DisplayQuery.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,145 @@
+package WebAppFramework::Unit::Database::DisplayQuery;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FragmentsTemplate';
+use WebAppFramework::ArgumentAdaptor;
+
+# new() parameters:
+#	Template => base name of template filename
+#   FragmentsName => name of fragments to pull out of the file
+#	Name => Name of the object (will be registered as a namespace)
+#	Query => Database::Query object which should be executed
+#	Args => Arguments
+#	PreQueryCode => code to output just before the query is made
+#	QueryCode => code to output to execute the query. Required if the class takes a runtime statement.
+#	PostQueryCode => code to output just after the query is executed.
+
+# Sub units will be displayed once per row in the returned table.
+
+# This Unit is intended to be used as a base for other objects.
+# Override write_header, write_row, write_footer for the basics!
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# initialisation
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# Make sure the template has been loaded
+		$self->ensure_template_loaded($output);
+
+		# register the namespace
+		$self->register_variable_namespace($$self{'Name'}, [$$self{'Query'}->get_results()]);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+	{
+		$output->write_code($$self{'Query'}->generate_h())
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_CODE)
+	{
+		$output->write_code($$self{'Query'}->generate_cpp())
+	}
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# Write the header of the table
+		$self->write_header($output, $phase, $subphase);
+
+		# execute the query
+		$output->write_code("{\n");
+		$output->write_code($$self{'PreQueryCode'}) if exists $$self{'PreQueryCode'};
+		if(exists $$self{'QueryCode'})
+		{
+			# Use the user supplied code to instantiate and execute the query
+			my $query_code = $$self{'QueryCode'};
+			$output->write_code($query_code);
+			# check that it mentioned the object, just to warn the user
+			my $nm = $$self{'Name'};
+			if($query_code !~ /$nm/)
+			{
+				die "On DisplayQuery, QueryCode supplied does not mention '$nm', which is the required name of the query object. Check your code!"
+			}
+		}
+		else
+		{
+			# check that the class isn't a runtime statment query
+			if($$self{'Query'}->is_runtime_statement())
+			{
+				die "On DisplayQuery, if you use a Query with a runtime statement, then you must supply the QueryCode to execute it."
+			}
+		
+			# generate the code for the query
+			$output->write_code($$self{'Query'}->get_name() . ' ' . $$self{'Name'} .
+					"(mApplication.GetDatabaseConnection());\n");
+			my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+				'Target' => $$self{'Query'},
+				'Source' => $self,
+				'Args' => $$self{'Args'});
+			$output->write_code($adaptor->generate_call($$self{'Name'}.'.Execute', ''));
+		}
+		$output->write_code($$self{'PostQueryCode'}) if exists $$self{'PostQueryCode'};
+		# begin the display loop
+		$output->write_code('while('.$$self{'Name'}.".Next())\n{\n");
+	}	
+
+	# Write the row
+	$self->write_row($output, $phase, $subphase);
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# end loop and query block
+		$output->write_code("}\n}\n");
+
+		# Write the footer
+		$self->write_footer($output, $phase, $subphase);
+	}
+}
+
+# Derived objects can override this
+# Called in HANDLE_OUTPUT phase only
+sub write_header
+{
+	my ($self, $output, $phase, $subphase) = @_;
+}
+
+# Derived objects can override this
+# Called in all phases
+sub write_row
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	# Write all the sub units in order
+	for(sort keys %{$$self{'_units'}})
+	{
+		# sub unit
+		${$$self{'_units'}}{$_}->write($output, $phase, $subphase);
+	}
+}
+
+# Derived objects can override this
+# Called in HANDLE_OUTPUT phase only
+sub write_footer
+{
+	my ($self, $output, $phase, $subphase) = @_;
+}
+
+# Derived objects can override this, but should always return the bits this returns!
+sub get_required_fragments
+{
+	return ();
+}
+
+# Derived objects can override this, but should always return the bits this returns!
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+	{
+		my $query = $$self{'Query'};
+		return ($query->dervied_from_DatabaseQueryGeneric())?('DatabaseQueryGeneric.h'):('DatabaseQuery.h');
+	}
+	return ()
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/ExecuteQuery.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/ExecuteQuery.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/ExecuteQuery.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,88 @@
+package WebAppFramework::Unit::Database::ExecuteQuery;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+use WebAppFramework::ArgumentAdaptor;
+use Database::Query;
+use CppVariable;
+
+# new() parameters:
+#	Name => Name of the object (will be registered as a namespace)
+#	Query => Database::Query object which should be executed
+#	Args => Arguments
+#	RedirectToOnNoRow => Page to redirect the user to if there's no row available (optional)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# ensure the query object is created
+	$self->ensure_query();
+	
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# Create an adaptor object
+		my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+			'Target' => $$self{'Query'},
+			'Source' => $self,
+			'Args' => $$self{'Args'});
+		# and store
+		$$self{'_adaptor'} = $adaptor;
+		
+		# register the namespace
+		$self->register_variable_namespace($$self{'Name'}, [$$self{'Query'}->get_results()]);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+	{
+		$output->write_code($$self{'Query'}->generate_h())
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_CODE)
+	{
+		$output->write_code($$self{'Query'}->generate_cpp())
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_VARS)
+	{
+		# create the object
+		$output->write_code($$self{'Query'}->get_name() . ' ' . $$self{'Name'} .
+				"(mApplication.GetDatabaseConnection());\n");
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_PREPARE)
+	{	
+		# execute the query
+		my $adaptor = $$self{'_adaptor'};
+		$output->write_code($adaptor->generate_call($$self{'Name'}.'.Execute', ''));
+		$output->write_code($$self{'Name'}.".Next();\n");
+		# handle the case where there's nothing there
+		if(exists $$self{'RedirectToOnNoRow'})
+		{
+			$output->write_code("if(!".$$self{'Name'}.".HaveRow())\n{\n");
+			$output->write_code($self->make_redirect_code(@{$$self{'RedirectToOnNoRow'}}));
+			$output->write_code("}\n");
+		}
+	}
+}
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+	{
+		return ('DatabaseQuery.h')
+	}
+	return ()
+}
+
+sub ensure_query()
+{
+	my ($self) = @_;
+	if(ref($$self{'Query'}) ne 'Database::Query')
+	{
+		# create a database query object as the given data isn't such an object
+		my %p = %{$$self{'Query'}};
+		my $q = Database::Query->new(%p);
+		$$self{'Query'} = $q;
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/FormNewOrEdit.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/FormNewOrEdit.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/FormNewOrEdit.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,366 @@
+package WebAppFramework::Unit::Database::FormNewOrEdit;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+use WebAppFramework::ArgumentAdaptor;
+use Database::Query;
+use CppVariable;
+use vars qw/%_autoname_start/;
+
+# new() parameters:
+#	NewCondition => C++ expression evaluating to true when the form is set to create a new object
+#	ReplaceFieldsInEdit => ['FieldName' => 'Replacement', ...] to replace fields when the form is in edit mode
+#	QueryNew => query(s) to create the object
+#	QueryRead => query(s) to read existing data about the object from the database
+#	QueryUpdate => query(s) to update the object in the database
+#	ArgsNew => For the New query(s), override finding of arguments from the form object
+#	ArgsRead => For the Read query(s), override finding of arguments from the form object
+#	ArgsUpdate => For the Update query(s), override finding of arguments from the form object
+#	ReadQueryNamespace => If present, the read query is registered as the named namespace, for access via page variables (in edit mode only!)
+#	ItemOverrideForUpdate => ['FieldName' => 'default', ...] to override then initial state for the update form
+#	RedirectTo => Page to redirect the user to after success
+#	RedirectOnNoReadResults => Page to redirect the use to if there are no results from the read
+#	PreExecuteCode => Any additional code to write before the database query is run
+#	PostExecuteCode => Any additional code to write after the database query is run
+
+# The form this refers to must be a positioned sub-unit of this unit.
+# The defaults in the items of the form should be set to those for the new version.
+# Defaults will be modified accordingly.
+
+# In the Query* parameters, the data is a Database::Query object which should be executed,
+# a hash array of Query constructor args (names automatically generated if not specified),
+# or a array of Database::Query or hash arrays to represent a list of queries which should
+# be executed.
+
+# If a New or Update query has an auto-increment value, then it will be made available
+# in the local variable <name>_AutoIncrementValue where <name> is the given name of the
+# query. It can therefore be accessed as a page variable like '=int32_t query_AutoIncrementValue'.
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE && !(exists $$self{'_initdone'}))
+	{
+		# find the form, which will be one of the sub units
+		my $form;
+		while(my ($k,$v) = each %{$$self{'_units'}})
+		{
+			die "Sub unit $k has unexpected type in FormNewOrEdit" unless ref($v) eq 'WebAppFramework::Unit::Form';
+			die "Multiple subunits are not allowed for FormNewOrEdit" if $form ne '';
+			$form = $v;
+		}
+		$$self{'_form'} = $form;
+		my $form_name = $form->get_form_name();
+		$$self{'_form_name'} = $form_name;
+		
+		# set conditional fields?
+		if(exists $$self{'ItemOverrideForUpdate'})
+		{
+			$form->conditional_items($form_name.'_InNewMode', @{$$self{'ItemOverrideForUpdate'}});
+		}
+		
+		# Build the lists of queries
+		$self->make_query_list('New');
+		$self->make_query_list('Read');
+		$self->make_query_list('Update');
+
+		# Register the read query as a namespace, if required
+		if(exists $$self{'ReadQueryNamespace'})
+		{
+			$self->register_variable_namespace($$self{'ReadQueryNamespace'},
+				sub
+				{
+					my ($namespace,$var) = @_;
+					$self->find_in_read_queries($var);
+				}
+			);
+		}
+
+		# Scan the form for form items, and modify the defaults
+		$form->interate_through_subunits(
+			sub
+			{
+				my ($unit,$parent) = @_;
+				if($unit->is_form_item())
+				{
+					# Unit is a form item, set a filter for the default value
+					$unit->set_default_value_filter(
+						sub
+						{
+							my ($form_item,$default_value,$cpp_type) = @_;
+							
+							# Attempt to find the read value in the read queries
+							my $read_value = $self->find_in_read_queries($form_item->get_item_name());
+							return $default_value unless defined $read_value;
+							
+							# Turn this into a cpp variable
+							my $def;
+							if(!defined($default_value) || $default_value eq '')
+							{
+								$def = cppvar($cpp_type, ($cpp_type eq 'std::string')?'std::string("")':'0');
+							}
+							elsif($default_value =~ m/\ACONSTANT:(.*)\Z/)
+							{
+								$def = cppvar($cpp_type, ($cpp_type eq 'std::string')?('std::string("'.$1.'")'):$1);
+							}
+							else
+							{
+								$def = $form_item->get_variable($default_value);
+							}
+							
+							# Now return a conditional expression
+							return cppvar($cpp_type, '('.$form_name.'_InNewMode)?('
+								. $def->convert_to($cpp_type)
+								. '):(' . $read_value->convert_to($cpp_type) . ')');
+						}
+					);
+				}
+				return 0;
+			}
+		);
+
+		# Add an on submit function to the form
+		$form->set('HandleSubmission',
+				sub
+				{
+					my ($page, $form, $output) = @_;
+					
+					# insert any additional code supplied by the user
+					if(exists $$self{'PreExecuteCode'})
+					{
+						$output->write_code($$self{'PreExecuteCode'});
+					}
+					# write code to submit the queries
+					$output->write_code("if(${form_name}_InNewMode)\n{\n");
+					
+					# write queries for the new mode
+					for my $q (@{$$self{'_queryListNew'}})
+					{
+						$output->write_code("{\n".$q->get_name()." q(mApplication.GetDatabaseConnection());\n");
+						my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+							'Target' => $q,
+							'Source' => $form,
+							'DefaultSourceObject' => $form_name,
+							'Args' => $$self{'ArgsNew'});
+						$output->write_code($adaptor->generate_call("q.Execute"));
+						if($q->has_autoincrement)
+						{
+							$output->write_code($q->get_name()."_AutoIncrementValue = q.InsertedValue();\n");
+						}
+						$output->write_code("}\n");
+					}
+					
+					# else clause
+					$output->write_code("}\nelse\n{\n");
+					
+					# write queries for the update mode
+					for my $q (@{$$self{'_queryListUpdate'}})
+					{
+						$output->write_code("{\n".$q->get_name()." q(mApplication.GetDatabaseConnection());\n");
+						my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+							'Target' => $q,
+							'Source' => $form,
+							'DefaultSourceObject' => $form_name,
+							'Args' => $$self{'ArgsUpdate'});
+						$output->write_code($adaptor->generate_call("q.Execute"));
+						if($q->has_autoincrement)
+						{
+							$output->write_code($q->get_name()."_AutoIncrementValue = q.InsertedValue();\n");
+						}
+						$output->write_code("}\n");
+					}
+					
+					# finish if statement
+					$output->write_code("}\n");
+
+					# any more additional code from the user?
+					if(exists $$self{'PostExecuteCode'})
+					{
+						$output->write_code($$self{'PostExecuteCode'});
+					}					
+					# redirect user?
+					if(exists $$self{'RedirectTo'})
+					{
+						$output->write_code($self->make_redirect_code(@{$$self{'RedirectTo'}}));
+					}
+				}
+			);
+		
+		# mark initialisaiton as done, as this phase may get called twice
+		$$self{'_initdone'} = 1;
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+	{
+		$self->output_queries($output, 1);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_CODE)
+	{
+		$self->output_queries($output, 0);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_VARS)
+	{
+		# output definitions for all the read queries
+		my $form_name = $$self{'_form_name'};
+		my $n = 0;
+		for(@{$$self{'_queryListRead'}})
+		{
+			$output->write_code($_->get_name()." ${form_name}_Read${n}(mApplication.GetDatabaseConnection());\n");
+			$n++
+		}
+		# any autoincrement values?
+		$self->define_autoinc_values($output, 'New');
+		$self->define_autoinc_values($output, 'Update');
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_PREPARE)
+	{
+		# output the c++ expression for the new/edit condition
+		my $form_name = $$self{'_form_name'};
+		$output->write_code("bool ${form_name}_InNewMode = (".$$self{'NewCondition'}.");\n");
+		# then, if the form hasn't been submitted, and we're not in new mode, read the data out
+		# from the database
+		$output->write_code(
+			(exists $$self{'ReadQueryNamespace'})
+				?("if(!${form_name}_InNewMode)\n{\n")	# query data required all the time if namespace is in use
+				:("if(!${form_name}_InNewMode && !${form_name}.WasSubmitted())\n{\n"));
+		$output->write_code("bool gotResults = true;\n") if exists $$self{'RedirectOnNoReadResults'};
+		my $n = 0;
+		for my $q (@{$$self{'_queryListRead'}})
+		{
+			my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+				'Target' => $q,
+				'Source' => $self,	# use this unit as a source, doesn't really matter exactly where it comes from
+				'Args' => $$self{'ArgsRead'});
+			$output->write_code($adaptor->generate_call("${form_name}_Read${n}.Execute"));
+			if(exists $$self{'RedirectOnNoReadResults'})
+			{
+				$output->write_code("if(!${form_name}_Read${n}.Next())\n{\ngotResults = false;\n}\n");
+			}
+			else
+			{
+				$output->write_code("${form_name}_Read${n}.Next();\n");
+			}
+			$n++;
+		}
+		if(exists $$self{'RedirectOnNoReadResults'})
+		{
+			$output->write_code("if(!gotResults)\n{\n");
+			$output->write_code($self->make_redirect_code(@{$$self{'RedirectOnNoReadResults'}}));
+			$output->write_code("}\n");
+		}
+		$output->write_code("}\n");
+	}
+	
+	# ask the based class to write everything else
+	WebAppFramework::Unit::write_unit($self, $output, $phase, $subphase);
+}
+
+sub make_query_list
+{
+	my ($self,$listname) = @_;
+
+	return if exists $$self{'_queryList'.$listname};
+	
+	die "in FormNewOrEdit, Query$listname parameter not specified" unless exists $$self{'Query'.$listname};
+	my $s = $$self{'Query'.$listname};
+	
+	my $r;
+	if(ref($s) eq 'Database::Query')
+	{
+		$r = [$s]
+	}
+	elsif(ref($s) eq 'HASH')
+	{
+		$r = [$self->hash_ref_to_query($s,$listname)]
+	}
+	elsif(ref($s) eq 'ARRAY')
+	{
+		$r = [map {$self->hash_ref_to_query($_,$listname)} @$s]
+	}
+	else
+	{
+		die "in FormNewOrEdit, Query$listname parameter has value of an unrecognised type"
+	}
+	
+	$$self{'_queryList'.$listname} = $r
+}
+
+%_autoname_start = ('New' => 0, 'Read' => 0, 'Update' => 0);
+sub hash_ref_to_query
+{
+	my ($self,$q,$namestart) = @_;
+	my %p = %$q;
+	if(!exists $p{'Name'})
+	{
+		# build a name
+		my $i = $_autoname_start{$namestart};
+		$_autoname_start{$namestart}++;
+		my $page = $self->get_pagename();
+		$p{'Name'} = 'fnoe_'.$page.$namestart.$i;
+	}
+	Database::Query->new(%p)
+}
+
+sub output_queries
+{
+	my ($self,$output,$output_h) = @_;
+
+	for my $n (qw/New Read Update/)
+	{
+		for my $q (@{$$self{'_queryList'.$n}})
+		{
+			$output->write_code($output_h
+					?($q->generate_h())
+					:($q->generate_cpp())
+				)
+		}
+	}
+}
+
+sub define_autoinc_values
+{
+	my ($self,$output,$type) = @_;
+	for my $q (@{$$self{'_queryList'.$type}})
+	{
+		if($q->has_autoincrement)
+		{
+			$output->write_code('int32_t '.$q->get_name()."_AutoIncrementValue = 0;\n");
+		}
+	}
+}
+
+
+sub find_in_read_queries
+{
+	my ($self,$name) = @_;
+	
+	my $n = 0;
+	for my $q (@{$$self{'_queryListRead'}})
+	{
+		my @results = $q->get_results();
+		for my $r (@results)
+		{
+			if($r->name() eq $name)
+			{
+				# found it, make a cpp variable for the value
+				return cppvar($r->type(),  $$self{'_form_name'}."_Read${n}.Get".$name.'()');
+			}
+		}
+		$n++;
+	}
+	
+	# not found
+	undef
+}
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+	{
+		return ('DatabaseQuery.h')
+	}
+	return ()
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/OnSubmitExecSQL.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/OnSubmitExecSQL.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/OnSubmitExecSQL.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,188 @@
+package WebAppFramework::Unit::Database::OnSubmitExecSQL;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+use WebAppFramework::ArgumentAdaptor;
+use Database::Query;
+use CppVariable;
+use vars qw/$_autoname_start/;
+# new() parameters:
+#	Query => Database::Query object which should be executed
+#	Args => Override finding of arguments from the form object
+#	RedirectTo => Page to redirect the user to after success
+#	PreExecuteCode => Any additional code to write before the database query is run
+#	PostExecuteCode => Any additional code to write after the database query is run
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# ensure the query object list is created
+	$self->make_query_list();
+	
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# find the form
+		my $form = $self;
+		while(ref($form) ne 'WebAppFramework::Unit::Form')
+		{
+			die "No form can be found by OnSubmitExecSQL" if $form eq '';
+			$form = $$form{'_parent'};
+		}
+		
+		# auto increment needed to be output?
+		my $auto_inc_count = 0;
+		for (@{$$self{'_query_list'}})
+		{
+			if($_->has_autoincrement())
+			{
+				$auto_inc_count++
+			}
+		}
+		
+		# Check the link, if provided, for special values
+		if(exists $$self{'RedirectTo'})
+		{
+			my $l = $$self{'RedirectTo'};
+			for(my $n = 2; $n <= $#$l; $n += 2)
+			{
+				if($$l[$n] eq 'QUERY_AUTO_INCREMENT_VALUE')
+				{
+					# need to change this to a local variable which will be created
+					$$l[$n] = cppvar('int32_t', 'autoIncrementValue');
+					# check have something to use!
+					unless($auto_inc_count == 1)
+					{
+						die "In OnSubmitExecSQL, redirect uses auto-inc value but query does not specify one, or multiple queries specify one"
+					}
+				}
+			}
+		}
+
+		# Add an on submit function to the form
+		$form->set('HandleSubmission',
+				sub
+				{
+					my ($page, $form, $output) = @_;
+					
+					# insert any additional code supplied by the user
+					if(exists $$self{'PreExecuteCode'})
+					{
+						$output->write_code($$self{'PreExecuteCode'});
+					}
+					# write code to submit the queries
+					for my $q (@{$$self{'_query_list'}})
+					{
+						# Create an adaptor object
+						my $adaptor = WebAppFramework::ArgumentAdaptor->new(
+							'Target' => $q,
+							'Source' => $form,
+							'DefaultSourceObject' => $form->get('FormName'),
+							'Args' => $$self{'Args'});
+
+						my $query_name = $q->get_name();
+						my $obj_name = $query_name.'_OnSubmit';
+						$output->write_code($adaptor->generate_call($obj_name.'.Execute', ''));
+
+						# Store auto inc value?
+						if($q->has_autoincrement())
+						{
+							if($auto_inc_count == 1)
+							{
+								# can write generic auto-inc value
+								$output->write_code("int32_t autoIncrementValue = $obj_name.InsertedValue();\n");
+							}
+							else
+							{
+								# use named auto-inc values
+								$output->write_code("int32_t ${query_name}_autoIncrementValue = $obj_name.InsertedValue();\n");
+							}
+						}
+					}
+					# any more additional code from the user?
+					if(exists $$self{'PostExecuteCode'})
+					{
+						$output->write_code($$self{'PostExecuteCode'});
+					}					
+					# redirect user?
+					if(exists $$self{'RedirectTo'})
+					{
+						$output->write_code($self->make_redirect_code(@{$$self{'RedirectTo'}}));
+					}
+				}
+			);
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+	{
+		$output->write_code($_->generate_h()) for (@{$$self{'_query_list'}})
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_CODE)
+	{
+		$output->write_code($_->generate_cpp()) for (@{$$self{'_query_list'}})
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_VARS)
+	{
+		for (@{$$self{'_query_list'}})
+		{
+			my $q_name = $_->get_name();
+			$output->write_code("$q_name ${q_name}_OnSubmit(mApplication.GetDatabaseConnection());\n");
+		}
+	}
+}
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+	{
+		return ('DatabaseQuery.h')
+	}
+	return ()
+}
+
+sub make_query_list
+{
+	my ($self) = @_;
+
+	return if exists $$self{'_query_list'};
+	
+	die "in OnSubitExecSQL, Query parameter not specified" unless exists $$self{'Query'};
+	my $s = $$self{'Query'};
+	
+	my $r;
+	if(ref($s) eq 'Database::Query')
+	{
+		$r = [$s]
+	}
+	elsif(ref($s) eq 'HASH')
+	{
+		$r = [$self->hash_ref_to_query($s)]
+	}
+	elsif(ref($s) eq 'ARRAY')
+	{
+		$r = [map {$self->hash_ref_to_query($_)} @$s]
+	}
+	else
+	{
+		die "in OnSubitExecSQL, Query parameter has value of an unrecognised type"
+	}
+	
+	$$self{'_query_list'} = $r
+}
+
+$_autoname_start = 0;
+sub hash_ref_to_query
+{
+	my ($self,$q) = @_;
+	my %p = %$q;
+	if(!exists $p{'Name'})
+	{
+		# build a name
+		my $i = $_autoname_start++;
+		my $page = $self->get_pagename();
+		$p{'Name'} = 'oses_'.$page.$i;
+	}
+	Database::Query->new(%p)
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/QueryObject.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/QueryObject.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/QueryObject.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,97 @@
+package WebAppFramework::Unit::Database::QueryObject;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+use Database::Query;
+
+# Unit to output a Database::Query written code
+
+# new() parameters:
+#	Query => Database::Query object, or specification
+#	Where => Global, Page, or Language -- which file to write it in
+#			(defaults to Page if not specified)
+
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# ensure the query object is created
+	$self->ensure_query();
+	
+	# default location to the page
+	$$self{'Where'} = 'Page' unless exists $$self{'Where'};
+
+	# write the code!
+	if($$self{'Where'} eq 'Global')
+	{
+		if($phase == WebAppFramework::Unit::PHASE_GLOBAL_H_DECLARATION)
+		{
+			$output->write_code($$self{'Query'}->generate_h());
+		}
+		elsif($phase == WebAppFramework::Unit::PHASE_GLOBAL_CPP_CODE)
+		{
+			$output->write_code($$self{'Query'}->generate_cpp());
+		}
+	}
+	elsif($$self{'Where'} eq 'Page')
+	{
+		if($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+		{
+			$output->write_code($$self{'Query'}->generate_h());
+		}
+		elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_DECLARATION)
+		{
+			$output->write_code($$self{'Query'}->generate_cpp());
+		}
+	}
+	elsif($$self{'Where'} eq 'Language')
+	{
+		if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_DECLARATION)
+		{
+			$output->write_code($$self{'Query'}->generate_h());
+			$output->write_code($$self{'Query'}->generate_cpp());
+		}
+	}
+	else
+	{
+		die 'Where location for WebAppFramework::Unit::QueryObject "'.$$self{'Where'}.'" is not valid'
+	}
+}
+
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+
+	if($$self{'Where'} eq 'Global')
+	{
+		if($type == WebAppFramework::Unit::HEADERS_GLOBAL_H_PROJECT)
+		{
+			return ('DatabaseQuery.h')
+		}
+	}
+	else
+	{
+		if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+		{
+			return ('DatabaseQuery.h')
+		}
+	}
+	return ()
+}
+
+
+sub ensure_query()
+{
+	my ($self) = @_;
+	if(ref($$self{'Query'}) ne 'Database::Query')
+	{
+		# create a database query object as the given data isn't such an object
+		my %p = %{$$self{'Query'}};
+		my $q = Database::Query->new(%p);
+		$$self{'Query'} = $q;
+	}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Table.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Table.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Database/Table.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,204 @@
+package WebAppFramework::Unit::Database::Table;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::Database::DisplayQuery';
+use WebAppFramework::ArgumentAdaptor;
+
+# new() parameters:
+#	Template => base name of template filename
+#   FragmentsName => name of fragments to pull out of the file
+#	Name => Name of the object (will be registered as a namespace)
+#	Query => Database::Query object which should be executed
+#	Args => Arguments
+#	HideFields => Array of fields to hide. (optional)
+
+# Add units as Column_Before_X to add a column before query result 'X'
+# Or Column_Last to add a column at the right hand side.
+# Append _<text> to name to have multiple columns added.
+# Have n_Heading to specify a heading unit for any column above, where n is the generated name.
+
+
+sub write_header
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# calculate columns and positions
+	$self->ensure_layout_calculated();
+
+	# Write top of table
+	$self->write_fragment_text($output, $phase, $subphase, 'Begin');
+	# Then the headings
+	$self->write_fragment_text($output, $phase, $subphase, 'RowBegin');
+	for(@{$$self{'_headings'}})
+	{
+		$self->write_fragment_text($output, $phase, $subphase, 'HeadingCellBegin');
+		if(ref($_))
+		{
+			# a Unit, rather than text
+			$_->write($output,$phase,$subphase);
+		}
+		else
+		{
+			# text, which needs translating
+			$output->write_text_translated($_)
+				if $phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT;
+		}
+		$self->write_fragment_text($output, $phase, $subphase, 'HeadingCellEnd');
+	}
+	$self->write_fragment_text($output, $phase, $subphase, 'RowEnd');
+}
+
+
+sub write_row
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# calculate columns and positions
+	$self->ensure_layout_calculated();
+
+	# write the data rows
+	$self->write_fragment_text($output, $phase, $subphase, 'RowBegin');
+
+	for(@{$$self{'_data'}})
+	{
+		$self->write_fragment_text($output, $phase, $subphase, 'DataCellBegin');
+		if(ref($_))
+		{
+			# a Unit, rather than a simple data member
+			$_->write($output,$phase,$subphase);
+		}
+		else
+		{
+			# variable, which simply needs to be output
+			$self->write_variable_text($output, $_)
+				if $phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT;
+		}
+		$self->write_fragment_text($output, $phase, $subphase, 'DataCellEnd');
+	}
+
+	$self->write_fragment_text($output, $phase, $subphase, 'RowEnd');
+}
+
+
+sub write_footer
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# finish table
+	$self->write_fragment_text($output, $phase, $subphase, 'End');
+}
+
+
+sub get_required_fragments
+{
+	my ($self) = @_;
+	return (
+		WebAppFramework::Unit::Database::DisplayQuery::get_required_fragments($self),
+		qw/Begin RowBegin HeadingCellBegin HeadingCellEnd DataCellBegin DataCellEnd RowEnd End/
+	);
+}
+
+sub ensure_layout_calculated
+{
+	my ($self) = @_;
+	return if exists $$self{'_headings'};
+
+	# get a list of column units
+	my @extra_columns = sort grep {m/\AColumn_/} keys %{$$self{'_units'}};
+	my %extra_columns_used = ();
+	
+	# create a hash of fields to ignore
+	my %ignore_field;
+	if(exists $$self{'HideFields'})
+	{
+		$ignore_field{$_} = 1 for(@{$$self{'HideFields'}});
+	}
+
+	# build lists
+	my $h = [];
+	my $d = [];
+	for($$self{'Query'}->get_results())
+	{
+		my $name = $_->name();
+		
+		# ignore?
+		next if exists $ignore_field{$name};
+
+		# Any columns preceding this unit?
+		$self->add_extra_columns('Column_Before_'.$name, \@extra_columns, \%extra_columns_used, $h, $d);
+		
+		# Does data display unit exist?
+		if(exists ${$$self{'_units'}}{$name.'_Display'})
+		{
+			# just use the unit
+			push @$d,${$$self{'_units'}}{$name.'_Display'}
+		}
+		else
+		{
+			# use the page variable for this
+			push @$d,$$self{'Name'}.'.'.$name
+		}
+		# Does heading unit exist?
+		if(exists ${$$self{'_units'}}{$name.'_Heading'})
+		{
+			# just use the unit
+			push @$h,${$$self{'_units'}}{$name.'_Heading'}
+		}
+		else
+		{
+			# make a default heading
+			my $default_heading_text = $name;
+			# put spaces before capital letters
+			$default_heading_text =~ s/([A-Z])/ $1/g;
+			$default_heading_text =~ s/\A //;
+			# store
+			push @$h,$default_heading_text
+		}
+	}
+	
+	# add in any extra columns which go at the end
+	$self->add_extra_columns('Column_Last', \@extra_columns, \%extra_columns_used, $h, $d);
+
+	# Warn about any unused colums
+	for(@extra_columns)
+	{
+		unless(exists $extra_columns_used{$_})
+		{
+			print "WARNING: Column Unit $_ is unused in WebAppFramework::Unit::Database::Table\n";
+		}
+	}
+
+	# store calculated layout
+	$$self{'_headings'} = $h;
+	$$self{'_data'} = $d;
+}
+
+sub add_extra_columns
+{
+	my ($self, $namebase, $extra_columns, $extra_columns_used, $h, $d) = @_;
+
+	for(@$extra_columns)
+	{
+		next unless m/\A$namebase(_|\Z)/;
+		next if m/_Heading\Z/;
+		# got a unit which goes before this column... add it to the data display list
+		push @$d,${$$self{'_units'}}{$_};
+		# mark as used
+		$$extra_columns_used{$_} = 1;
+		# see if there's a heading for it
+		if(exists ${$$self{'_units'}}{$_.'_Heading'})
+		{
+			# Yes, use that unit as the heading
+			push @$h,${$$self{'_units'}}{$_.'_Heading'};
+			# mark as used
+			$$extra_columns_used{$_.'_Heading'} = 1;
+		}
+		else
+		{
+			# Use the blank string, as no heading is provided
+			push @$h,''
+		}
+	}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FixedPointNumber.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FixedPointNumber.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FixedPointNumber.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,26 @@
+package WebAppFramework::Unit::FixedPointNumber;
+use strict;
+use base 'WebAppFramework::Unit';
+use WebAppFramework::FixedPoint;
+
+# Unit to output a fixed point number
+
+# new() parameters:
+#	Variable => name of variable
+#	ScaleDigits => number of digits for (base 10) fixed point values
+#	DisplayDigits => min number of digits to use for the fractional part. Defaults to ScaleDigits.
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# use utility function
+		WebAppFramework::FixedPoint::write_fixed_point_value($output,
+			$self->get_variable($$self{'Variable'}),
+			$$self{'ScaleDigits'}, $$self{'DisplayDigits'});
+	}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Form.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Form.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Form.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,739 @@
+package WebAppFramework::Unit::Form;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FragmentsTemplate';
+use vars qw/%_allocated_ids/;
+use CppDataClass;
+use CppVariable;
+use WebAppFramework::Unit::OutputIf;
+
+# define constants before using field modules
+use constant OUTPUT_SUB_PHASE_DEFAULT => 0;
+use constant OUTPUT_SUB_PHASE_REDISPLAY => 1;
+
+# can use phase values in range 80 -- 99
+
+
+use WebAppFramework;
+use WebAppFramework::Output;
+use WebAppFramework::Unit::FormTableContainer;
+
+
+# new() parameters:
+#	FormName => name of form
+#	FormID => one letter ID (only necessary if target is not this page)
+#	ExternalErrors => errors are listed externally to the form
+#	HandleSubmission => code which handles the submission of the form, or ref to function which writes it (optional)
+#	FormValidation => none, simple, errorgen -- style of extra validation
+#	ArgsToValidate => which objects should be passed to the Validate() function
+#			Space separated list (or anon array) of Application, Request, Response.
+#	PostSetAndValidateCode => optional, code which is output just after the form object is read and validated.
+#	ErrorDisplayCondition => C++ condition for displaying the error display box (the one by default above the form)
+#			If 'false', the code is not output.
+
+# Add sub units, which contain FormItems, or use make_container to return a new
+# FormTableContainer.
+# Sub units are output in alphabetical order of position.
+
+sub get_form_name
+{
+	my ($self) = @_;
+	return $$self{'FormName'}
+}
+
+sub make_container
+{
+	my ($self, @a) = @_;
+	
+	my $i = 0;
+	while(exists ${$$self{'_units'}}{'CONTAINER'.$i})
+	{
+		$i++
+	}
+	my $container = WebAppFramework::Unit::FormTableContainer->new(@a);
+	$self->add_unit('CONTAINER'.$i, $container);
+	$container
+}
+
+# fragments required
+sub get_required_fragments
+{
+	my ($self) = @_;
+	return qw/ErrorMarker/;
+}
+
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+
+	return ($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)?('WebAppForm.h'):();
+}
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	$self->ensure_template_loaded($output, $phase, $subphase);
+
+	# has a form ID been allocated?
+	unless(exists $$self{'_form_id'})
+	{
+		if(exists $$self{'FormID'})
+		{
+			# author specified a form ID in initialisation
+			my $i = $$self{'FormID'};
+			
+			die "Form ID '$i' is not exactly 1 character in length"
+				unless length($i) == 1;
+			
+			die "Form ID already allocated for form name ".$$self{'FormName'}
+				if exists $$self{'_form_id'};
+			
+			die "Form ID '$i' has already been used on this page"
+				if exists $_allocated_ids{$i};
+			
+			$$self{'_form_id'} = $i;
+			$_allocated_ids{$i} = $self;
+		}
+		else
+		{
+			# no form ID specified, generate one automatically
+			for('a' .. 'z', 'A' .. 'Z')
+			{
+				unless(exists $_allocated_ids{$_})
+				{
+					$$self{'_form_id'} = $_;
+					$_allocated_ids{$_} = $self;
+					last;
+				}
+			}
+			die "Could not automatically allocate ID for form ".$$self{'FormName'}." -- too many forms on the page?"
+				unless exists $$self{'_form_id'};
+		}
+	}
+	my $form_id = $$self{'_form_id'};
+	
+	# Parse the user validation setting
+	my $has_user_validation = 0;
+	my $user_validation_is_error_generating = 0;
+	if(exists $$self{'FormValidation'})
+	{
+		if($$self{'FormValidation'} eq 'none')
+		{
+		}
+		elsif($$self{'FormValidation'} eq 'simple')
+		{
+			$has_user_validation = 1;
+		}
+		elsif($$self{'FormValidation'} eq 'errorgen')
+		{
+			$has_user_validation = 1;
+			$user_validation_is_error_generating = 1;
+		}
+		else
+		{
+			die "Unknown FormValidation style '".$$self{'FormValidation'}."'"
+		}
+	}
+	
+	# The following initialisation cannot be done in the initialise phase
+	# because not all page variables will exist.
+	if($phase != WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# parse any extra arguments required by the validation function
+		unless(exists $$self{'_args_setandvalidate'})
+		{
+			my $args_setandvalidate = '';
+			my $args_callsetandvalidate;
+			my @args_validate;
+			my @args_callvalidate;
+			
+			if(exists $$self{'ArgsToValidate'})
+			{
+				my $webappname = $self->get_webapp()->get_webapp_name();
+			
+				# process each entry in turn
+				for my $a ($self->list_to_array($$self{'ArgsToValidate'}))
+				{
+					# one of the known items?
+					if($a eq 'Application')
+					{
+						$args_setandvalidate .= ', '.$webappname.' &rApplication';
+						$args_callsetandvalidate .= ', mApplication';
+						push @args_validate,$webappname.' &rApplication';
+						push @args_callvalidate,'rApplication';
+					}
+					elsif($a eq 'Response')
+					{
+						$args_setandvalidate .= ', HTTPResponse &rResponse';
+						$args_callsetandvalidate .= ', rResponse';
+						push @args_validate,'HTTPResponse &rResponse';
+						push @args_callvalidate,'rResponse';
+					}
+					elsif($a eq 'Request')
+					{
+						# no set and validate arg, it's already passed
+						push @args_validate,'const HTTPRequest &rRequest';
+						push @args_callvalidate,'rRequest';
+					}
+					else
+					{
+						# it's a page variable
+						my $var = $self->get_variable($a);
+						
+						# generate a name for it by replacing all non-alphanum chars with _
+						my $nm = (($a =~ m/\A=/)?($var->name()):($a));
+						$nm =~ tr/0-9a-zA-Z/_/c;
+						$nm =~ s/_+\Z//;
+						
+						# type for arguments (pass as reference if a composite type)
+						my $ty = $var->type();
+						$ty = "const $ty&" if $var->is_composite_type();
+						
+						# build the various arguments and declarations...
+						$args_setandvalidate .= ", $ty $nm";
+						$args_callsetandvalidate .= ', '.$var->name();
+						push @args_validate,"$ty $nm";
+						push @args_callvalidate,$nm;
+					}
+				}
+			}
+			
+			# store for later
+			$$self{'_args_setandvalidate'} = $args_setandvalidate;
+			$$self{'_args_callsetandvalidate'} = $args_callsetandvalidate;
+			$$self{'_args_callvalidate'} = join(', ',@args_callvalidate);
+			
+			# add a functions to the data class?
+			die "internal error, initialisation bad order" unless exists $$self{'_data_class'};
+			if($has_user_validation)
+			{
+				# declare the validation function
+				$$self{'_data_class'}->add_declarations(CppDataClass::PROTECTED, 'void Validate('.join(', ',@args_validate).');');
+			}
+			$$self{'_data_class'}->add_declarations(CppDataClass::PUBLIC, "void SetAndValidate(const HTTPRequest &rRequest$args_setandvalidate);");
+		}
+	}
+	
+	# Initialise everything
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# get a list of all form items, in random order
+		my @items = map {($_->isa('WebAppFramework::Unit::FormItem'))?$_:()} $self->flatten_heirarchy();
+		
+		# allocate IDs and collect form object variables in alphabetical order
+		my %i;
+		for(@items)
+		{
+			$i{$_->get_item_name()} = $_
+		}
+		my $idn = 0;
+		my @object_vars;
+		my @valid_vars;
+		my @valid_fns;
+		for(sort keys %i)
+		{
+			my $item = $i{$_};
+			
+			my $form_ids_required = $item->get_num_ids_required();
+			my @ids;
+			for(my $z = 0; $z < $form_ids_required; $z++)
+			{
+				push @ids,$form_id.sprintf("%03x", $idn++);
+			}
+			$item->set_ids(@ids);
+			
+			# collect variables
+			my @fv = $item->get_form_variables();
+			if($#fv >= 0)
+			{
+				# object has variables... add
+				push @object_vars, @fv;
+				# and a single 'valid' variable, if the item does validation, that is
+				unless($item->always_passes_validation())
+				{
+					my $in = $item->get_item_name();
+					push @valid_vars, cppvar('int8_t', $in.'ValidityError', $item->default_validation_state());
+					push @valid_fns, "bool ${in}Valid() {return (m${in}ValidityError == WebAppForm::Valid);}";
+				}
+			}
+		}
+		
+		# add in any condition variables for the conditional items
+		my @itemconditions_vars;
+		my $num_itemconditions = $#{$$self{'_item_conditions'}} + 1;
+		for(my $i = 0; $i < $num_itemconditions; $i++)
+		{
+			push @itemconditions_vars, cppvar('bool','_ItemCondition'.$i)
+		}
+
+		# create a C++ class which represents the form
+		my $webapp = $self->get_webapp();
+		my $data_class = CppDataClass->new($webapp->get_webapp_name().'Form'.ucfirst($$self{'FormName'}),
+			$user_validation_is_error_generating?'WebAppFormCustomErrors':'WebAppForm',
+			@object_vars, @valid_vars, @itemconditions_vars, cppvar('bool _FormValid false'), cppvar('bool _WasSubmitted false'));
+		# don't write Set() functions -- this is read only
+		$data_class->set_option('ReadOnly', 1);
+		# add extra bits of data and functions
+		$data_class->add_declarations(CppDataClass::PUBLIC,
+			$user_validation_is_error_generating
+				?'bool FormValid() {return Get_FormValid() && !HaveErrorText();}'
+				:'bool FormValid() {return Get_FormValid();}',
+			'bool WasSubmitted() {return Get_WasSubmitted();}',
+			'void SetFormToInvalid() {m_FormValid = false;}',
+			'void SetFormToUnsubmitted() {m_WasSubmitted = false; m_FormValid = false;}',
+			@valid_fns);
+		# and a function to set the item conditions?
+		if($num_itemconditions > 0)
+		{
+			my (@a,@i);
+			for(my $i = 0; $i < $num_itemconditions; $i++)
+			{
+				push @a,"bool c$i";
+				push @i,"m_ItemCondition$i = c$i;"
+			}
+			$data_class->add_declarations(CppDataClass::PUBLIC,
+				'void _SetItemConditions('.join(', ',@a).') {'.join(' ',@i).'}'
+			);
+		}
+
+		# store for later
+		$$self{'_data_class'} = $data_class;
+		
+		# register with the base class so things can access it
+		$self->register_variable_namespace($$self{'FormName'}, $data_class->get_data_members_ref());
+		
+		# initialise everything else
+		$self->write_subunits($output, $phase, $subphase);
+		
+		# stop there
+		return;
+	}
+
+	my $data_class = $$self{'_data_class'};
+
+	# write error display (if it hasn't been relocated)
+	$self->write_error_display($output, $phase, $subphase)
+		unless exists $$self{'_externally_displayed_errors'};
+
+	# write stuff on various output phases
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_VARS)
+	{
+		# declare the variable, and get the contents set up from the request
+		$output->write_code("\t".$data_class->get_class_name().' '.$$self{'FormName'}.";\n");
+		# set up strings, if it's an error generating form
+		if($user_validation_is_error_generating)
+		{
+			$output->write_code("\t".$$self{'FormName'}.".SetStrings("
+				.WebAppFramework::Output::string_to_cpp_static_string($self->get_fragment('ErrorListSeparate'))
+				.", PageTranslatedStrings);\n");
+		}
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_PREPARE)
+	{
+		# feed in any item conditions
+		if($#{$$self{'_item_conditions'}} >= 0)
+		{
+			$output->write_code($$self{'FormName'}.'._SetItemConditions('
+				.join(', ',@{$$self{'_item_conditions'}})
+				.");\n")
+		}
+
+		# get data
+		my $sav_args = $$self{'_args_callsetandvalidate'};
+		$output->write_code("\t".$$self{'FormName'}.".SetAndValidate(rRequest$sav_args);\n");
+		$output->write_code($$self{'PostSetAndValidateCode'}) if exists $$self{'PostSetAndValidateCode'};
+		# output code to do the magic when the form is submitted?
+		if(exists $$self{'HandleSubmission'})
+		{
+			$output->write_code("\tif(".$$self{'FormName'}.".WasSubmitted() && ".$$self{'FormName'}.".FormValid())\n\t{\n");
+			if(ref($$self{'HandleSubmission'}))
+			{
+				# function which write the code
+				&{$$self{'HandleSubmission'}}($self->get_root(), $self, $output)
+			}
+			else
+			{
+				# just plain text
+				$output->write_code($$self{'HandleSubmission'})
+			}
+			$output->write_code("\t}\n");
+		}
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# handle writing of sub units (which includes the form fields)
+
+		# Output two possibilities for the form, one in the default state,
+		# and another for when it needs to be redisplayed.
+		
+		$output->write_code("\tif(!".$$self{'FormName'}.".WasSubmitted())\n\t{\n");
+		
+			$self->write_form_start($output);
+			$self->write_subunits($output, $phase, OUTPUT_SUB_PHASE_DEFAULT);
+			$self->write_form_end($output);
+
+		$output->write_code("\t}\n\telse\n\t{\n");
+
+			$self->write_form_start($output);
+			$self->write_subunits($output, $phase, OUTPUT_SUB_PHASE_REDISPLAY);
+			$self->write_form_end($output);
+
+		$output->write_code("\t}\n");
+		
+		# stop here, as the sub-units are already done
+		return;
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_H_DECLARATION)
+	{
+		$output->write_code($data_class->generate_h());
+	}
+	elsif($phase == WebAppFramework::Unit::PHASE_MAIN_CPP_CODE)
+	{
+		# get list of all items
+		my @items = map {($_->isa('WebAppFramework::Unit::FormItem'))?$_:()} $self->flatten_heirarchy();
+
+		# Write the data class boilerplate
+		$output->write_code($data_class->generate_cpp());
+		# write the function to read all the variables from the request
+		my $dcn = $data_class->get_class_name();
+		my $extraargs = $$self{'_args_setandvalidate'};
+		my $fid = $$self{'_form_id'};
+		$output->write_code(<<__E);
+void ${dcn}::SetAndValidate(const HTTPRequest &rRequest$extraargs)
+{
+	HTTPRequest::Query_t query(rRequest.GetQuery());
+
+	m_FormValid = false;
+	m_WasSubmitted = (query.find("${fid}_s") != query.end());
+
+	if(!m_WasSubmitted)
+	{
+		// Form wasn't submitted, don't attempt to process further
+		return;
+	}
+	
+__E
+
+		# If conditions are set, mark such items as valid
+		# This means that they will be kept marked as valid, unless a value actually does sneak through
+		if(exists $$self{'_conditional_formitems'})
+		{
+			$output->write_code("// Conditionally displayed form items need to have default values changed\n");
+			for(@items)
+			{
+				next unless $_->requires_acceptor_code();
+				my $name = $_->get_item_name();
+				if(exists ${$$self{'_conditional_formitems'}}{$name})
+				{
+					my $num = ${$$self{'_conditional_formitems'}}{$name};
+					$output->write_code("if(!m_ItemCondition$num) {m${name}ValidityError = WebAppForm::Valid;}\n");
+				}
+			}
+			$output->write_code("\n");
+		}
+
+		$output->write_code(<<__E);
+	// Go through all form elements in turn, reading them into variables
+	for(HTTPRequest::Query_t::const_iterator i(query.begin()); i != query.end(); ++i)
+	{
+		switch(WebApplication::FourCharStringToInt(i->first.c_str()))
+		{
+__E
+		for(@items)
+		{
+			next unless $_->requires_acceptor_code();
+			# write the item's accept code
+			for my $id ($_->get_ids())
+			{
+				my $ide = WebAppFramework::element_to_string($id);
+				$output->write_code("case $ide: // $id, ".$_->get_item_name()."\n{\n");
+				$_->write_value_acceptance_code($output, cppvar('std::string', '(i->second)'),
+					($_->always_passes_validation())?'':('m'.$_->get_item_name().'ValidityError'), $id);
+				$output->write_code("}\nbreak;\n");
+			}
+		}
+
+		$output->write_code(<<__E);
+		default:
+			// Ignore, not one of ours
+			break;
+		}
+	}
+
+__E
+
+		if($has_user_validation)
+		{
+			my $args = $$self{'_args_callvalidate'};
+			$output->write_code(<<__E);
+	// Extra validation (app author implements this function)
+	Validate($args);
+
+__E
+		}
+	
+		my @validation_items = map
+			{
+				($_->requires_acceptor_code() && !($_->always_passes_validation()))?'(m'.$_->get_item_name().'ValidityError == WebAppForm::Valid)':()
+			} @items;
+	
+		if($#validation_items >= 0)
+		{
+			$output->write_code(<<__E);
+	// Is the entire form valid?
+	if(
+__E
+	$output->write_code('    '.join("\n    && ", @validation_items)."\n");
+	$output->write_code(<<__E);
+		)
+	{
+		m_FormValid = true;
+	}
+}
+__E
+		}
+		else
+		{
+			# nothing to validate, just mark as valid and finish the function
+			$output->write_code("m_FormValid = true;\n}\n");
+		}
+	}
+
+	# Get sub-units to write their stuff too
+	$self->write_subunits($output, $phase, $subphase);
+}
+
+
+sub conditional_items
+{
+	my ($self, $condition, @items) = @_;
+	
+	# condition is the condition for the fields to be shown.
+	# %items key is fieldname or ref to unit, value is text or unit to replace it
+	# with when it's not shown, undef if nothing to replace with.
+	
+	# Check variables are created
+	if(!exists $$self{'_item_conditions'})
+	{
+		$$self{'_item_conditions'} = [];
+		$$self{'_conditional_formitems'} = {};
+	}
+	
+	# Work out the condition number, creating a new condition if necessary
+	my $condition_num = 0;
+	while($condition_num <= $#{$$self{'_item_conditions'}})
+	{
+		last if ${$$self{'_item_conditions'}}[$condition_num] eq $condition;
+		$condition_num++;
+	}
+	if($condition_num >= $#{$$self{'_item_conditions'}})
+	{
+		push @{$$self{'_item_conditions'}},$condition
+	}
+	
+	# Now run through and make the items actually conditional
+	while($#items >= 0)
+	{
+		my ($i,$replacement);
+		($i,$replacement,@items) = @items;
+		if(ref($i))
+		{
+			# it's a reference to a unit, which must be scanned for other units
+			# find all the names of the relevant form items
+			my $items_found = 0;
+			my $fn = 
+				sub
+				{
+					my ($unit,$parent) = @_;
+					if($unit->is_form_item())
+					{
+						$items_found++;
+						my $name = $unit->get_item_name();
+						${$$self{'_conditional_formitems'}}{$name} = $condition_num;
+					}
+					return 0;
+				};
+			&$fn($i,$self);
+			$i->interate_through_subunits($fn);
+			die "No items found within unit passed to conditional_items" unless $items_found > 0;
+			# and build the appropraite conditional unit
+			my $parent = $i->get_parent();
+			my $conditional = WebAppFramework::Unit::OutputIf->new(
+						'Condition' => $$self{'FormName'}.".Get_ItemCondition${condition_num}()",
+						'@true' => $i,
+					);
+			$conditional->add_unit('false', $replacement) if defined $replacement;
+			$parent->replace_unit($i, $conditional);
+		}
+		else
+		{
+			# it's the name of the form item, which must be found and replaced
+			# with a conditional unit
+			unless($self->interate_through_subunits(
+					sub
+					{
+						my ($unit,$parent) = @_;
+						if($unit->is_form_item())
+						{
+							if($unit->get_item_name() eq $i)
+							{
+								# This is the right item
+								${$$self{'_conditional_formitems'}}{$i} = $condition_num;
+								my $conditional = WebAppFramework::Unit::OutputIf->new(
+											'Condition' => $$self{'FormName'}.".Get_ItemCondition${condition_num}()",
+											'@true' => $unit,
+										);
+								$conditional->add_unit('false', $replacement) if defined $replacement;
+								$parent->replace_unit($unit, $conditional);
+								return 1;
+							}
+						}
+						return 0;
+					}
+				)
+			)
+			{
+				die "FormItem $i cannot be found"
+			}
+		}
+	}
+}
+
+
+sub write_error_display
+{	
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	return unless $phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT;
+	
+	# display of this conditional?
+	my $disp_condition = $$self{'ErrorDisplayCondition'};
+	return if $disp_condition eq 'false';
+	if($disp_condition ne '')
+	{
+		$output->write_code("if($disp_condition)\n{\n");
+	}
+	
+	# error generating data class?
+	my $user_validation_is_error_generating = ($$self{'FormValidation'} eq 'errorgen');
+	
+	$output->write_code("\tif(".$$self{'FormName'}.".WasSubmitted() && !".$$self{'FormName'}.".FormValid())\n\t{\n// Display error message for form\n");
+	$output->write_code("bool firstDone = false;\n") if exists $$self{'ExternalErrors'} || $user_validation_is_error_generating;	# avoid breaking up text
+	$output->write_text($self->get_fragment('ErrorStart'));
+
+	# want to do external errors?
+	if($user_validation_is_error_generating || exists $$self{'ExternalErrors'})
+	{
+		# write bits of error message	
+		$output->write_text($self->get_fragment('ErrorListStart'));
+	}
+	
+	if($user_validation_is_error_generating)
+	{
+		# will always be first, so can skip the writing of the separator
+		$output->write_code('if('.$$self{'FormName'}.".HaveErrorText())\n{\n");
+		$output->write_code("firstDone = true;\n");
+		$output->write_code("rResponse.WriteString(".$$self{'FormName'}.".GetErrorText());\n");		
+		$output->write_code("}\n");
+	}
+	
+	if(exists $$self{'ExternalErrors'})
+	{
+		# get list of all items, in form order
+		my @items = map {($_->isa('WebAppFramework::Unit::FormItem'))?$_:()} $self->flatten_heirarchy();
+		for(@items)
+		{
+			# some don't have data or always passes validation
+			next unless $_->requires_acceptor_code();
+			next if $_->always_passes_validation();
+	
+			# code checks to see if it's valid, writes a separator if it's not the first in the list
+			$output->write_code("if(!".$self->get_is_item_valid_expression($_).")\n{\nif(firstDone)\n{\n");
+			$output->write_text($self->get_fragment('ErrorListSeparate'));
+			$output->write_code("}\nelse\n{\nfirstDone = true;\n}\n");
+			# then gets the item to write it's own error message
+			$_->write_validation_fail_message($output);
+			# and finishes
+			$output->write_code("}\n");
+		}
+	}
+	
+	if($user_validation_is_error_generating || exists $$self{'ExternalErrors'})
+	{
+		$output->write_text($self->get_fragment('ErrorListEnd'));
+	}
+
+	$output->write_code("}\n");
+
+	# finish the overall conditional display block?
+	if($disp_condition ne '')
+	{
+		$output->write_code("}\n");
+	}
+}
+
+sub write_subunits
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	for(sort keys %{$$self{'_units'}})
+	{
+		${$$self{'_units'}}{$_}->write($output, $phase, $subphase);
+	}
+}
+
+sub write_form_start
+{
+	my ($self,$output) = @_;
+	$output->write_text('<form method="POST"><input type="hidden" name="'.$$self{'_form_id'}.'_s">');
+}
+
+sub write_form_end
+{
+	my ($self,$output) = @_;
+	$output->write_text("</form>");
+}
+
+# given a FormItem, return an expression which evaulate to true if the
+# field is valid
+sub get_is_item_valid_expression
+{
+	my ($self,$item) = @_;
+	
+	if((!$item->requires_acceptor_code()) || $item->always_passes_validation())
+	{
+		die "Item type ".ref($item).", name ",$item->get_item_name().", does not provide acceptor code, or is always valid, cannot determine if entry is valid"
+	}
+	
+	'('.$$self{'FormName'}.'.'.$item->get_item_name().'Valid())';
+}
+
+# get the name of the variable
+sub get_item_validity_error
+{
+	my ($self,$item) = @_;
+	
+	if((!$item->requires_acceptor_code()) || $item->always_passes_validation())
+	{
+		die "Item type ".ref($item).", name ",$item->get_item_name().", does not provide acceptor code, or is always valid, cannot determine if entry is valid"
+	}
+
+	$$self{'FormName'}.'.Get'.$item->get_item_name().'ValidityError()';	
+}
+
+sub get_form_template_fragment
+{
+	my ($self,$frag_name) = @_;
+	return $self->get_fragment($frag_name);
+}
+
+# whether or not error messages are displayed inline
+sub inline_error_messages
+{
+	my ($self) = @_;
+	return !exists $$self{'ExternalErrors'};
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormErrorDisplay.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormErrorDisplay.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormErrorDisplay.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,25 @@
+package WebAppFramework::Unit::FormErrorDisplay;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Arguments to new()
+#	Form => reference to form
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# set var in form to stop it doing stuff itself
+		${$$self{'Form'}}{'_externally_displayed_errors'} = 1;
+	}
+
+	# Ask the form to write the display here
+	$$self{'Form'}->write_error_display($output, $phase, $subphase);
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Checkbox.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Checkbox.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Checkbox.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,115 @@
+package WebAppFramework::Unit::FormItem::Checkbox;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FormItem';
+use CppVariable;
+
+# new() parameters:
+#	Name => Form item name
+#	Label => Value text (will be translated)
+# 	Default => source of default value (can be CONSTANT:true/false for constant values)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# Generate attributes
+		my $attr = ' type="checkbox" name="'.$self->get_id().'"';
+		$attr .= $self->get_tag_attributes();
+
+		# write the item
+		if($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+		{
+			$output->write_text("<input$attr");
+			
+			# get original default
+			my $default;
+			my $constant_default = undef;
+			if(exists $$self{'Default'})
+			{
+				if($$self{'Default'} =~ m/\ACONSTANT:(.+?)\Z/)
+				{
+					my $c = $1;
+					die "Checkbox FormItem constant default must be true or false"
+						unless $c eq 'true' || $c eq 'false';
+					$default = cppvar('bool', $1);
+					$constant_default = ($c eq 'true');
+				}
+				else
+				{
+					$default = $self->get_variable($$self{'Default'})
+				}
+			}
+			else
+			{
+				$default = cppvar('bool', 'false');
+				$constant_default = 0;
+			}
+			# filter default
+			{
+				my $d = $self->filter_default_value($default, 'bool');
+				if($d != $default)
+				{
+					$constant_default = undef;
+					$default = $d;
+				}
+			}
+			
+			# write default value
+			if(defined $default)
+			{			
+				if(defined $constant_default)
+				{
+					if($constant_default)
+					{
+						$output->write_text(" checked");
+					}
+				}
+				else
+				{
+					$output->write_code("if(".$default->convert_to('bool').")\n{\n");
+					$output->write_text(" checked");
+					$output->write_code("}\n");
+				}
+			}
+		}
+		else
+		{
+			# is item already checked
+			$output->write_text("<input$attr");
+			my $v = $self->get_variable($self->get_value_page_variable());
+			$output->write_code("if(".$v->convert_to('bool').")\n{\n");
+			$output->write_text(" checked");
+			$output->write_code("}\n");
+		}
+		$output->write_text(">");
+		$output->write_text_translated($$self{'Label'});
+		$output->write_text("</input>");
+	}
+}
+
+# Field is always a bool
+sub get_form_variable_type
+{
+	my ($self) = @_;
+	return ('bool')
+}
+
+sub write_value_acceptance_code
+{
+	my ($self, $output, $data_source, $validity_error_name) = @_;
+	
+	$output->write_code('m'.$self->get_item_name()." = true;\n");
+	die "Internal logic error" unless $validity_error_name eq '';
+}
+
+# check boxes can't be validated
+sub always_passes_validation
+{
+	my ($self) = @_;
+	return 1;
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Choice.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Choice.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Choice.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,574 @@
+package WebAppFramework::Unit::FormItem::Choice;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FormItem';
+use CppVariable;
+use WebAppFramework::Unit::DataSource::StaticStrings;
+
+# new() parameters:
+#	Name => Form item name
+#	Choices => | separated list of choices (will be translated) -- if a sub-unit DataSource isn't added
+# 	Default => CONSTANT: untranslated default choice (from list), or variable name. If not included, a blank item will be displayed
+#	Style => select (HTML select widget) or items (checkboxes or radio buttons)
+#	Size => rows visible (for select mode only)
+#	Columns => number of columns (for items mode only)
+#	Validation =>
+#		none	- no checking (single choice widget)
+#		single	- single choice must be made (single choice widget)
+#		choices(x,y)	- numbers allowed within range x to y inclusive. Omit for unbounded range. (multiple choice widget)
+
+# If a sub-unit at position DataSource exists, it will be used as a source of the options.
+# These should be derived from WebAppFramework::Unit::DataSource.
+
+
+# the maximum number of choices written as inline code, rather than a loop
+use constant MAX_INLINE_CHOICES =>	8;
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# Type of C++ variable
+		$self->ensure_cpptype_found();
+		my $cpptype = $$self{'_cpptype'};
+	
+		# Are the choices in this control bound to another data source?
+		my $binding = undef;
+		my @choices_un;
+		my @choices;
+		if(exists ${$$self{'_units'}}{'DataSource'})
+		{
+			$binding = ${$$self{'_units'}}{'DataSource'};
+		}
+		else
+		{
+			# untranslated choices (for default checking)
+			@choices_un = split /\|/,$$self{'Choices'};
+			# translated items (for display)
+			@choices = split /\|/,$output->translate_text($$self{'Choices'});
+			
+			# check!
+			die "Choices form item ".$$self{'Name'}." has no choices set"
+				if $#choices_un < 0;
+			die "Choices form item ".$$self{'Name'}.", translation has different number of choices"
+				if $#choices_un != $#choices;
+		}
+		
+		# default?
+		my $constant_default = -1;
+		my $var_default = undef;
+		if(exists $$self{'Default'})
+		{
+			if($$self{'Default'} =~ m/\ACONSTANT:(.+?)\Z/)
+			{
+				die "Cannot use a CONSTANT:default in Choices FormItem when a DataBinding is in use"
+					unless $#choices_un >= 0;		# don't check on $binding, because it might be an automatic StaticStrings binding
+
+				my $def = $1;
+				for(my $i = 0; $i <= $#choices_un; $i++)
+				{
+					if($choices_un[$i] eq $def)
+					{
+						$constant_default = $i;
+						last
+					}
+				}
+				die "Choices form item ".$$self{'Name'}.", default $def cannot be found in choices list"
+					if $constant_default == -1;
+			}
+			else
+			{
+				$var_default = $self->get_variable($$self{'Default'});
+			}
+		}
+		# Filter the default
+		{
+			my $v = (defined $var_default)
+						?$var_default
+						:cppvar($cpptype, ($cpptype eq 'std::string')?qq!"$constant_default"!:$constant_default);
+			my $v_f = $self->filter_default_value($v, $cpptype);
+			if($v_f != $v)
+			{
+				# the filter changed something, use the new value
+				$constant_default = -1;
+				$var_default = $v_f;
+			}
+		}
+		
+		# useful stuff...
+		my $select_style = $self->_is_select_style();
+		my $attr = $self->get_tag_attributes();
+		my $id = $self->get_id();
+		my $choose_multiple = $self->_can_choose_multiple();
+		my $input_type = ($choose_multiple)?'checkbox':'radio';
+		my $size = (exists $$self{'Size'})?(int($$self{'Size'})):1;
+		$size = 1 if $size <= 0;
+		# make sure that multiple choice things have at least two items to play with
+		$size = 2 if $choose_multiple && $size < 2;
+		# columns?
+		my $columns = 0;
+		if(!$select_style)
+		{
+			$columns = int($$self{'Columns'}) if exists $$self{'Columns'}
+		}
+
+		# start boilerplate?
+		if($select_style)
+		{
+			my $s = ($size == 1)?'':qq` size="$size"`;
+			my $m = ($choose_multiple)?' multiple':'';
+			$output->write_text(qq`<select name="$id"$s$m$attr>`);
+			if(!(exists $$self{'Default'}) && $size == 1)
+			{
+				# no default... write blank entry
+				$output->write_text(qq`<option value="">`.$output->translate_text('-- select --').'</option>')
+			}
+		}
+		elsif($columns > 0)
+		{
+			# Is multiple columns item style form item -- output table header
+			$output->write_text('<table cellpadding=0 cellspacing=2 border=0><tr><td>')
+		}
+		
+		my $constant_output = (!defined($var_default) && $subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT);
+		
+		if(!$constant_output)
+		{
+			# non-constant field default, or in second phase...
+			my $value_source = ($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+				?$var_default:$self->get_variable($self->get_value_page_variable());
+			if($choose_multiple && $subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_REDISPLAY)
+			{
+				$output->write_code("{\nconst std::vector<${cpptype}> &v(".$value_source->convert_to("std::vector<${cpptype}>").");\n");
+			}
+			else
+			{
+				$output->write_code("{\n${cpptype} v = ".$value_source->convert_to($cpptype).";\n");
+			}
+		}
+		
+		# Binding or static choices?
+		if(defined $binding)
+		{
+			if(!$select_style && $columns > 0)
+			{
+				# need to keep a separate count of the cells output
+				$output->write_code("int32_t cellsWritten = 0;\n");
+			}
+
+			# Generate a function to write the code
+			my $function;
+			my $strings_trusted = $binding->string_is_trusted();
+			$function = sub
+				{
+					my ($key_source,$string_source,$output,$phase,$subphase) = @_;
+
+					# if condition for selection
+					my $if_cond;
+					if($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_REDISPLAY)
+					{
+						# form is being redisplayed
+						$if_cond = ($choose_multiple)
+							?"std::find(v.begin(), v.end(), $key_source) != v.end()":"v == $key_source";
+					}
+					else
+					{
+						# initial form display -- only one choice
+						$if_cond = ($constant_output)
+							?("$key_source == $constant_default"):("v == $key_source")
+					}
+		
+					$output->write_text($select_style?'<option value="':qq`<input type="$input_type" name="$id" value="`);
+					if($cpptype eq 'int32_t')
+					{
+						$output->write_code(<<__E);
+							{
+								char str[32];
+								rResponse.Write(str, ::sprintf(str, "%d", $key_source));
+							}
+__E
+					}
+					else
+					{
+						# The keys can't necessarily be trusted either
+						if($strings_trusted)
+						{
+							$output->write_code("rResponse.WriteString($key_source);\n");
+						}
+						else
+						{
+							$output->write_code("rResponse.WriteStringDefang($key_source);\n");
+						}
+					}
+					# finish off the option, with the selection thing
+					if(!$constant_output || $constant_default != -1)
+					{
+						$output->write_code("if($if_cond)\n{\n");
+						$output->write_text($select_style?'" selected>':'" checked>');
+						$output->write_code("}\nelse\n{\n");
+					}
+					$output->write_text('">');
+					if(!$constant_output || $constant_default != -1)
+					{
+						$output->write_code("}\n");
+					}
+					# write the bound data
+					if($strings_trusted)
+					{
+						$output->write_code("rResponse.WriteString($string_source);\n");
+					}
+					else
+					{
+						$output->write_code("rResponse.WriteStringDefang($string_source);\n");
+					}
+					$output->write_text($select_style?'</option>':'</input>');
+					if(!$select_style)
+					{
+						# got to write a separator here...
+						if($columns > 0)
+						{
+							# going to need some code here...
+							my $tv = $columns-1;
+							$output->write_code("if((cellsWritten % $columns) == $tv)\n{\n");
+							$output->write_text('</td></tr><tr><td>');
+							$output->write_code("}\nelse\n{\n");
+							$output->write_text('</td><td>');
+							$output->write_code("}\n++cellsWritten;\n");
+						}
+						else
+						{
+							# no columns, just use <br>
+							$output->write_text('<br>');
+						}
+					}
+				};
+		
+			# Ask the binding to write the necessary code
+			$binding->write_bound_item($function, $output, $phase, $subphase);
+		}
+		else
+		{
+			# Do each item in turn
+			for(my $i = 0; $i <= $#choices; $i++)
+			{
+				my $choosen_test = ($choose_multiple && $subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_REDISPLAY)
+							?"std::find(v.begin(), v.end(), $i) != v.end()":"v == $i";
+						
+				if($select_style)
+				{
+					$output->write_text(qq`<option value="$i"`);
+					if($constant_output)
+					{
+						$output->write_text(($i == $constant_default)?' selected':'');
+					}
+					else
+					{
+						$output->write_code("if($choosen_test)\n{\n");
+						$output->write_text(' selected');
+						$output->write_code("}\n");				
+					}
+					$output->write_text('>'.$choices[$i].'</option>');
+				}
+				else
+				{
+					$output->write_text(qq`<input type="$input_type" name="$id" value="$i"`);
+					if($constant_output)
+					{
+						$output->write_text(($i == $constant_default)?' checked':'');
+					}
+					else
+					{
+						$output->write_code("if($choosen_test)\n{\n");
+						$output->write_text(' checked');
+						$output->write_code("}\n");				
+					}
+					$output->write_text($attr.'>'.$choices[$i].'</input>');
+					if($columns > 0)
+					{
+						# columns, write table rows/column separator
+						if($i != $#choices && (($i % $columns) == ($columns-1)))
+						{
+							# new row
+							$output->write_text('</td></tr><tr><td>')
+						}
+						else
+						{
+							# new cell
+							$output->write_text('</td><td>')
+						}
+					}
+					else
+					{
+						# no columns, just use <br>
+						$output->write_text('<br>') if $i != $#choices;
+					}
+				}
+			}
+		}
+		
+		$output->write_code("}\n")
+			unless $constant_output;
+
+		# end boilerplate
+		if($select_style)
+		{
+			$output->write_text("</select>")
+		}
+		elsif($columns > 0)
+		{
+			# Is multiple columns item style form item -- output table footer
+			$output->write_text('</td></tr></table>')
+		}
+	}
+	
+	# use the default Unit write_unit to make sure things like data sources
+	# have a chance to write all their other stuff required
+	WebAppFramework::Unit::write_unit($self, $output, $phase, $subphase);
+}
+
+# get variables, make sure they have the right default
+sub get_form_variables
+{
+	my ($self) = @_;
+	$self->ensure_cpptype_found();
+	my $cpptype = $$self{'_cpptype'};
+	if($self->_can_choose_multiple())
+	{
+		return (cppvar("std::vector<${cpptype}>", $$self{'Name'}))
+	}
+	else
+	{
+		return (cppvar($cpptype, $$self{'Name'}, ($cpptype eq 'std::string')?undef:'WebAppForm::NoChoiceMade'))
+	}
+}
+
+# if validation is 'none', then it'll always pass validation
+sub always_passes_validation
+{
+	my ($self) = @_;
+	my $validation = $self->get_item_validation();
+	return ($validation eq 'none' || $validation =~ m/\Achoices\(\s*,\s*\)\Z/)
+}
+
+
+sub write_value_acceptance_code
+{
+	my ($self, $output, $data_source, $validity_error_name) = @_;
+	
+	$self->ensure_cpptype_found();
+	my $cpptype = $$self{'_cpptype'};
+
+	# validation method
+	my $validation = $self->get_item_validation();
+	
+	# data value within the form
+	my $dv = 'm'.$self->get_item_name();
+		
+	# decode the value given
+	my $in = $data_source->convert_to('std::string');
+	if($cpptype eq 'int32_t')
+	{
+		# integer type
+		my ($min, $max);
+		if(exists ${$$self{'_units'}}{'DataSource'})
+		{
+			my $binding = ${$$self{'_units'}}{'DataSource'};
+			($min,$max) = $binding->get_integer_range();
+		}
+		else
+		{
+			$min = 0;
+			# calc max choice number from the untranslated strings
+			my @choices_un = split /\|/,$$self{'Choices'};
+			$max = $#choices_un + 1;
+		}
+		my $min_c = ($min eq '')?'':" || v < $min";
+		my $max_c = ($max eq '')?'':" || v > $max";
+		
+		$output->write_code(<<__E);
+			errno = 0;	// necessary on some platforms
+			int32_t v = ::strtol($in.c_str(), NULL, 10);
+			if(!(($in).empty() || (v == 0 && errno == EINVAL) || v == LONG_MIN || v == LONG_MAX$min_c$max_c))
+			{
+__E
+	}
+	else
+	{
+		# string type
+		$output->write_code("std::string v($in);\n{\n");
+	}
+	
+	if($self->_can_choose_multiple())
+	{
+		# multiple choices
+		$output->write_code("$dv.push_back(v);\n}\n");
+		
+		# validation
+		die "Bad validation $validation for Choices FormItem"
+			unless $validation =~ m/\Achoices\((\d*)\s*,\s*(\d*)\)\Z/;
+		my ($min,$max) = ($1,$2);
+		if($min ne '' && $max eq '')
+		{
+			$output->write_code("$validity_error_name = ($dv.size() >= $min)?WebAppForm::Valid:WebAppForm::NotValid;\n")
+		}
+		elsif($min eq '' && $max ne '')
+		{
+			$output->write_code("$validity_error_name = ($dv.size() <= $max)?WebAppForm::Valid:WebAppForm::NotValid;\n")
+		}
+		elsif($min ne '' && $max ne '')
+		{
+			$output->write_code("$validity_error_name = ($dv.size() >= $min && $dv.size() <= $max)?WebAppForm::Valid:WebAppForm::NotValid;\n")
+		}
+	}
+	else
+	{
+		# single choice only
+		$output->write_code("$dv = v;\n}\n");
+
+		# validate
+		if($validation ne 'none')
+		{
+			my $cond = ($cpptype eq 'int32_t')?"$dv != WebAppForm::NoChoiceMade":"!($dv.empty())";
+			$output->write_code("$validity_error_name = ($cond)?WebAppForm::Valid:WebAppForm::NotValid;\n")
+		}
+	}
+}
+
+sub make_validation_fail_message
+{
+	my ($self) = @_;
+	
+	my $inline_errs = $self->get_form()->inline_error_messages();
+	my $label = $self->get_item_label();
+
+	my $t;
+
+	if($self->_can_choose_multiple())
+	{
+		my $validation = $self->get_item_validation();
+		
+		$validation =~ m/\Achoices\((\d*)\s*,\s*(\d*)\)\Z/;
+		my ($min,$max) = ($1,$2);
+		if($min ne '' && $max eq '')
+		{
+			$t = "Please choose at least $min options"
+		}
+		elsif($min eq '' && $max ne '')
+		{
+			$t = "Please choose no more than $max options"
+		}
+		elsif($min ne '' && $max ne '')
+		{
+			$t = "Please choose between $min and $max options"
+		}
+	}
+	else
+	{
+		$t = 'Please choose an option'
+	}
+	
+	if(!$inline_errs)
+	{
+		$t .= ' for '.$label
+	}
+	
+	$t
+}
+
+# will need some extra headers
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	
+	my @h;
+
+	# Standard headers required
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_SYSTEM)
+	{
+		push @h,'stdlib.h','limits.h','errno.h';
+
+		if($self->_can_choose_multiple())
+		{
+			push @h,'vector','algorithm';
+		}
+	}
+
+	# for integer conversions (used with DataSources, too complex to make it worth including
+	# only if there is a DataSource subunit because of when we know we've got such a unit.)
+	if($type == WebAppFramework::Unit::HEADERS_SYSTEM)
+	{
+		push @h,'stdio.h';
+	}
+
+	@h
+}
+
+
+sub ensure_cpptype_found
+{
+	my ($self) = @_;
+	return if exists $$self{'_cpptype'};
+
+	# See what C++ type we'll be using, count the number of choices
+	my $cpptype = 'int32_t';
+	if(exists ${$$self{'_units'}}{'DataSource'})
+	{
+		my $binding = ${$$self{'_units'}}{'DataSource'};
+		$cpptype = 'std::string' unless $binding->key_is_integer();
+	}
+	else
+	{
+		# automatically use a StaticStrings DataSource for large amounts of choices?
+		my $l = $$self{'Choices'};
+		$l =~ tr/|//cd;
+		if(length($l) >= MAX_INLINE_CHOICES)
+		{
+			$self->add_unit('DataSource',
+				WebAppFramework::Unit::DataSource::StaticStrings->new('Strings' => $$self{'Choices'}));
+		}
+	}
+
+	# and store
+	$$self{'_cpptype'} = $cpptype;
+}
+
+sub _is_select_style
+{
+	my ($self) = @_;
+	
+	if($$self{'Style'} eq 'select')
+	{
+		return 1;
+	}
+	elsif($$self{'Style'} eq 'items')
+	{
+		return 0
+	}
+	else
+	{
+		die "Choices form item ".$$self{'Name'}.", style is not select or items"
+	}
+}
+
+sub _can_choose_multiple
+{
+	my ($self) = @_;
+
+	my $validation = $self->get_item_validation();
+
+	if($validation eq 'single' || $validation eq 'none')
+	{
+		# not multiple choice
+		return 0;
+	}
+	else
+	{
+		# multiple things can be selected
+		return 1;
+	}
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Date.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Date.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/Date.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,285 @@
+package WebAppFramework::Unit::FormItem::Date;
+use strict;
+use base 'WebAppFramework::Unit::FormItem';
+use CppVariable;
+use WebAppFramework::DateAndTime;
+use vars qw/%letter_to_index %letter_to_name/;
+
+# new() parameters:
+#	Name => Form item name
+# 	Default => standard date specifier
+#	Optional => set if the field is optional
+#	StartYear => first year displayed
+#	EndYear => last year displayed
+#	ValidationFailMsg => text to display on error
+
+# used for allocating IDs
+%letter_to_index = ('Y' => 0, 'M' => 1, 'D' => 2);
+# used for getting translated text
+%letter_to_name = ('Y' => 'Year', 'M' => 'Month', 'D' => 'Day');
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# work out where the default values for the field come from
+		my @defaults = ('','','');
+		if(exists $$self{'Default'})
+		{
+			@defaults = WebAppFramework::DateAndTime::make_YMD_params_array(
+				$self,
+				$self->list_to_array($$self{'Default'})
+			);
+		}
+
+		# get the locale
+		my $locale = $self->get_locale();
+		
+		# get the order in which the fields are to be written from the locale
+		my $order = $locale->get('FormItemDateOrdering');
+
+		# IDs used
+		my @element_ids = $self->get_ids();
+		
+		# What's the object in the form?
+		my $current_value_name = $self->get_form()->get_form_name().'.Get'.$self->get_item_name().'()';
+		
+		# start off the table which contains the form
+		$output->write_text('<table cellpadding=0 cellspacing=4 border=0><tr>');
+		
+		# write each item in turn
+		for my $element_letter (split //,$order)
+		{
+			my $element_id = $element_ids[$letter_to_index{$element_letter}];
+			my $unselected_text = '-- '.$locale->get($letter_to_name{$element_letter}).' --';
+		
+			if($element_letter eq 'Y')
+			{
+				# start the item
+				$output->write_text(qq!<td><select name="$element_id"><option value="">$unselected_text</option>!);
+				
+				# write the for loop
+				my $start = int($$self{'StartYear'});
+				my $end = int($$self{'EndYear'});
+
+				# if they're zero, then it's a page variable
+				$start = $self->get_variable($$self{'StartYear'})->convert_to('int32_t') if $start == 0;
+				$end = $self->get_variable($$self{'EndYear'})->convert_to('int32_t') if $end == 0;
+				
+				# Finally that for loop
+				my $selected_val = ($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+						?($defaults[$letter_to_index{$element_letter}])
+						:($current_value_name.'.GetYear()');
+				my $selval = ($selected_val eq '')?'':"\nint selectedValue = ($selected_val);";
+				$output->write_code(<<__E);
+					{
+						int yearStart = ($start);	// in case it's expensive to get these values
+						int yearEnd = ($end);
+						int inc = (yearStart < yearEnd)?+1:-1;$selval
+						for(int year = yearStart; year != yearEnd; year += inc)
+						{
+							char output[128];
+__E
+				if($selected_val eq '')
+				{
+					$output->write_code(qq!rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\">%04d</option>", year, year));\n!);
+				}
+				else
+				{
+					$output->write_code(<<__E);
+							if(year == selectedValue)
+							{
+								rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\" selected>%04d</option>", year, year));
+							}
+							else
+							{
+								rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\">%04d</option>", year, year));
+							}
+__E
+				}
+
+				$output->write_code(<<__E);
+						}
+					}			
+__E
+
+				# end the item
+				$output->write_text(qq!</select></td>!);
+			}
+			elsif($element_letter eq 'M')
+			{
+				# start the item
+				$output->write_text(qq!<td><select name="$element_id"><option value="">$unselected_text</option>!);
+				
+				# Write the code
+				my $selected_val = ($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+						?($defaults[$letter_to_index{$element_letter}])
+						:($current_value_name.'.GetMonth()');
+				my $selval = ($selected_val eq '')?'':"\nint selectedValue = ($selected_val);";
+				$output->write_code(<<__E);
+					{
+						// Get the month names from the locale
+						const char **monthNames = locale.GetMonthList();$selval
+						for(int month = 1; month <= 12; ++month)
+						{
+							char output[128];
+__E
+				if($selected_val eq '')
+				{
+					$output->write_code(qq!rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\">%s</option>", month, monthNames[month]));\n!);
+				}
+				else
+				{
+					$output->write_code(<<__E);
+							if(month == selectedValue)
+							{
+								rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\" selected>%s</option>", month, monthNames[month]));
+							}
+							else
+							{
+								rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\">%s</option>", month, monthNames[month]));
+							}
+__E
+				}
+				
+				$output->write_code(<<__E);
+						}
+					}			
+__E
+				# end the item
+				$output->write_text(qq!</select></td>!);
+			}
+			elsif($element_letter eq 'D')
+			{
+				# start the item
+				$output->write_text(qq!<td><select name="$element_id"><option value="">$unselected_text</option>!);
+
+				# Finally write that for loop
+				my $selected_val = ($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+						?($defaults[$letter_to_index{$element_letter}])
+						:($current_value_name.'.GetDay()');
+				if($selected_val eq '')
+				{
+					# initiall it's an unselected value, so just precompute the entire list of values
+					for(my $day = 1; $day <= 31; $day++)
+					{
+						$output->write_text(qq!<option value="$day">$day</option>!);
+					}
+				}
+				else
+				{
+					# a value needs to be selected
+					$output->write_code(<<__E);
+						{
+							int selectedValue = ($selected_val);
+							for(int day = 1; day <= 31; ++day)
+							{
+								char output[128];
+__E
+					if($selected_val eq '')
+					{
+						$output->write_code(qq!rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\">%d</option>", day, day));\n!);
+					}
+					else
+					{
+						$output->write_code(<<__E);
+								if(day == selectedValue)
+								{
+									rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\" selected>%d</option>", day, day));
+								}
+								else
+								{
+									rResponse.Write(output, ::sprintf(output, "<option value=\\"%d\\">%d</option>", day, day));
+								}
+__E
+					}
+	
+					$output->write_code(<<__E);
+							}
+						}			
+__E
+				}
+
+				# end the item
+				$output->write_text(qq!</select></td>!);
+			}
+			else
+			{
+				die "Unknown element letter $element_letter specified in Locale FormItemDateOrdering item"
+			}
+		}
+	
+		# close the table
+		$output->write_text('</tr></table>');
+	}
+}
+
+sub write_value_acceptance_code
+{
+	my ($self, $output, $data_source, $validity_error_name, $element_id) = @_;
+	
+	# which field is this?
+	my @element_ids = $self->get_ids();
+	my @element_names = qw/Year Month Day/;
+	my $element_name;
+	for(my $l = 0; $l < 3; $l++)
+	{
+		$element_name = $element_names[$l] if $element_ids[$l] eq $element_id;
+	}
+	$output->write_code($validity_error_name .= ' = m'.$self->get_item_name().'.DataFromForm(WAFFormItemDate::'
+			.$element_name.', '.$data_source->convert_to('std::string').'.c_str(), '
+			.(($$self{'Optional'} ne '')?'true':'false').");\n");
+}
+
+sub write_validation_fail_message
+{
+	my ($self, $output) = @_;
+	
+	if(exists $$self{'ValidationFailMsg'})
+	{
+		$output->write_text_translated($$self{'ValidationFailMsg'})
+	}
+	else
+	{
+		$output->write_text_translated('Please enter a valid date')
+	}
+}
+
+# need the data type header in the page definition
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+	{
+		return ('WAFFormItemDate.h')
+	}
+	elsif($type == WebAppFramework::Unit::HEADERS_SYSTEM)
+	{
+		return ('stdio.h')
+	}
+	return ()
+}
+
+# Three ids are required for this form item: year, month, day
+sub get_num_ids_required
+{
+	return 3;
+}
+
+# use a special class for the data type
+sub get_form_variable_type
+{
+	return 'WAFFormItemDate'
+}
+
+# if the form item is optional, then the default validation state is Valid
+sub default_validation_state
+{
+	my ($self) = @_;
+	return ($$self{'Optional'} ne '')?'WebAppForm::Valid':'WebAppForm::NotValid'
+}
+
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/ExtraDataMember.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/ExtraDataMember.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/ExtraDataMember.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,57 @@
+package WebAppFramework::Unit::FormItem::ExtraDataMember;
+use strict;
+use base 'WebAppFramework::Unit::FormItem';
+use CppVariable;
+
+# new() parameters:
+#	Variable => CppVariable style specification of data member
+
+sub new
+{
+	my ($type, @params) = @_;
+	
+	# construct and interpret variable specification
+	my $self = WebAppFramework::Unit::new($type, @params);
+	$$self{'_var'} = cppvar($$self{'Variable'});
+	
+	# Generate a name for the form
+	$$self{'Name'} = $$self{'_var'}->name().'__extradatamember';
+
+	$self
+}
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+}
+
+# by default, no form variable is used for submit buttons
+# If the result should be reported, then the name followed by 'Clicked' is reported
+sub get_form_variables
+{
+	my ($self) = @_;
+
+	return (cppvar($$self{'Variable'}))
+}
+
+# no acceptor code should be written
+sub requires_acceptor_code()
+{
+	my ($self) = @_;
+	return 0;
+}
+
+# shouldn't ever display an error marker
+sub disable_error_marker
+{
+	return 1;
+}
+
+# doesn't do any validation
+sub always_passes_validation
+{
+	return 1
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/NumberField.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/NumberField.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/NumberField.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,284 @@
+package WebAppFramework::Unit::FormItem::NumberField;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FormItem';
+use CppVariable;
+use WebAppFramework::FixedPoint;
+
+# new() parameters:
+#	Name => Form item name
+# 	Default => source of default value (can be CONSTANT:value for constant values)
+#	DefaultNumber => a default number to use when conversion fails completely
+#	FixedPointScaleDigits => number of digits for (base 10) fixed point values
+#	FixedPointDisplayDigits => min number of digits to use for the fractional part. Defaults to FixedPointScaleDigits.
+#			note: When fixed point in use, specify all numbers in human readable form, eg 1.01
+#	BlankValue => a special value to use when the field is blank (blank not allowed if this value is not present)
+#	Size => size of field
+#	Validation =>
+#		none	- no range checking
+#		range(x,y)	- numbers allowed within range x to y inclusive. Omit for unbounded range.
+
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# Generate attributes
+		my $attr = '" name="'.$self->get_id().'"'.$self->get_tag_attributes();
+		my $size = int($$self{'Size'});
+		$size = 12 if $size <= 0;
+		
+		# Work out maxlength attribute
+		my $max_len = -1;
+		my $validation = $self->get_item_validation();
+		if($validation =~ m/\Arange\((-?\d*)\s*,\s*(-?\d*)\)\Z/)
+		{
+			# use values to determine the size of the field
+			if($1 ne '' && $2 ne '')
+			{
+				$max_len = length($1);
+				$max_len = length($2) if length($2) > $max_len;
+				
+				# take into account the fixed-pointness
+				if(exists $$self{'FixedPointScaleDigits'})
+				{
+					# add on the number of digits, plus room for the '.'
+					$max_len += int($$self{'FixedPointScaleDigits'}) + 1;
+				}
+			}
+		}
+		# make sure it has a sensible default, which is 11, the size of the largest integer that can be expected
+		$max_len = 11 if $max_len <= 0;
+	
+		$attr .= qq` size="$size" maxlength="$max_len"`;
+
+		# write the item
+		if($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+		{
+			$output->write_text('<input type="text"'.$attr);
+
+			# write a default value?
+			my $def;
+			my $blank_val = (exists $$self{'BlankValue'})?($$self{'BlankValue'}):'0x7fffffff';
+			if(exists $$self{'Default'})
+			{
+				if($$self{'Default'} =~ m/\ACONSTANT:(.*)\Z/)
+				{
+					$def = cppvar('int32_t', $self->specified_value_to_int($1))
+				}
+				else
+				{
+					$def = $self->get_variable($$self{'Default'})
+				}
+			}
+			else
+			{
+				$def = cppvar('int32_t', $blank_val);
+			}
+			my $def_f = $self->filter_default_value($def, 'int32_t');
+			if($def_f == $def)
+			{
+				# no filtering (if it's a constant, it'll end up as static text)
+				if(exists $$self{'Default'})
+				{
+					$output->write_text(' value="');
+					# special behaviour for fixed point
+					if(exists $$self{'FixedPointScaleDigits'})
+					{
+						# constant value?
+						if($$self{'Default'} =~ m/\ACONSTANT:(.*)\Z/)
+						{
+							# just write whatever the user specified
+							$output->write_text($1);
+						}
+						else
+						{
+							# write it as a fixed point value
+							WebAppFramework::FixedPoint::write_fixed_point_value($output,
+								$self->get_variable($def), $$self{'FixedPointScaleDigits'}, $$self{'FixedPointDisplayDigits'});
+						}
+					}
+					else
+					{
+						$self->write_variable_text($output, $$self{'Default'});
+					}
+					$output->write_text('"');
+				}
+			}
+			else
+			{
+				$output->write_text(' value="');
+				my $d = $def_f->convert_to('int32_t');
+				$output->write_code(<<__E);
+					{
+						int32_t v = $d;
+						if(v != $blank_val)
+						{
+__E
+				if(exists $$self{'FixedPointScaleDigits'})
+				{
+					# write it as a fixed point value
+					WebAppFramework::FixedPoint::write_fixed_point_value($output,
+						$self->get_variable('=int32_t v'), $$self{'FixedPointScaleDigits'}, $$self{'FixedPointDisplayDigits'});
+				}
+				else
+				{
+					$self->write_variable_text($output, '=int32_t v');
+				}
+				$output->write_code("}\n}\n");
+				$output->write_text('"');			
+			}
+
+			$output->write_text('>');
+		}
+		else
+		{
+			my $bv_check = '';
+			if(exists $$self{'BlankValue'})
+			{
+				$bv_check = $self->get_variable($self->get_value_page_variable())->convert_to('int32_t')." != ".$self->specified_value_to_int($$self{'BlankValue'})." && "
+			}		
+			$output->write_text('<input type="text"'.$attr.' value="');
+			my $v = $self->get_form()->get_item_validity_error($self);
+			$output->write_code("if(${bv_check}$v != WebAppForm::NumberFieldErr_Blank && $v != WebAppForm::NumberFieldErr_FormatBlank)\n{\n");
+			if(exists $$self{'FixedPointScaleDigits'})
+			{
+				# write it as a fixed point value
+				WebAppFramework::FixedPoint::write_fixed_point_value($output,
+					$self->get_variable($self->get_value_page_variable()), $$self{'FixedPointScaleDigits'}, $$self{'FixedPointDisplayDigits'});
+			}
+			else
+			{
+				$self->write_variable_text($output, $self->get_value_page_variable());
+			}
+			$output->write_code("}\n");
+			$output->write_text('">');
+		}
+	}
+}
+
+sub specified_value_to_int
+{
+	my ($self,$v) = @_;
+	return int($v) unless exists $$self{'FixedPointScaleDigits'};
+	
+	# how many digits?
+	my $digits = int($$self{'FixedPointScaleDigits'});
+	
+	# adjust the value
+	my $mul = 1;
+	for(my $l = 0; $l < $digits; $l++) {$mul *= 10;}
+	return int($v * $mul);
+}
+
+sub write_value_acceptance_code
+{
+	my ($self, $output, $data_source, $validity_error_name) = @_;
+	
+	# validation method
+	my $validation = $self->get_item_validation();
+	
+	# data value within the form
+	my $dv = 'm'.$self->get_item_name();
+	
+	my ($range_begin,$range_end) = ('','');
+
+	# check the validation string
+	if($validation eq 'none')
+	{
+		# leave both ends of the range blank
+	}
+	elsif($validation =~ m/\Arange\((-?[\d\.]*)\s*,\s*(-?[\d\.]*)\)\Z/)
+	{
+		($range_begin,$range_end) = ($1,$2)
+	}
+	else
+	{
+		# bad value!
+		die "Bad validation for NumberField: ".$validation
+	}
+	
+	# data value within the form
+	my $dv = 'm'.$self->get_item_name();
+	
+	my $blank_val = '0';
+	my $blank_allowed = 'false';
+	if(exists $$self{'BlankValue'})
+	{
+		$blank_val = $$self{'BlankValue'};
+		$blank_allowed = 'true';
+	}
+	
+	# handle the fixed point case
+	my $fnname_extra = '';
+	my $fnargs_extra = '';
+	if(exists $$self{'FixedPointScaleDigits'})
+	{
+		$fnname_extra = 'FixedPoint';
+		$fnargs_extra = ', '.int($$self{'FixedPointScaleDigits'});
+	}
+	
+	# store the data value
+	$output->write_code($validity_error_name ." = WAFUtility::IntepretNumberField${fnname_extra}("
+		.$data_source->convert_to('std::string').
+		", $dv, ".$self->specified_value_to_int($blank_val).", $blank_allowed, "
+		.$self->specified_value_to_int($range_begin).", "
+		.$self->specified_value_to_int($range_end).", ".
+		(($range_begin eq '')?'false':'true').", ".(($range_end eq '')?'false':'true')."$fnargs_extra);\n");
+}
+
+# get variables, make sure they have the right default
+sub get_form_variables
+{
+	my ($self) = @_;
+	die "NumberField doesn't have DefaultNumber specified"
+		unless exists $$self{'DefaultNumber'};
+	return (cppvar('int32_t', $$self{'Name'}, $$self{'DefaultNumber'}))
+}
+
+# the possible values of the validition fail error
+sub get_values_of_validation_fail_messages
+{
+	return qw/WebAppForm::NumberFieldErr_Range WebAppForm::NumberFieldErr_Format|WebAppForm::NumberFieldErr_FormatBlank WebAppForm::NumberFieldErr_Blank/;
+}
+
+sub make_validation_fail_message
+{
+	my ($self, $err_val) = @_;
+
+	my $inline_errs = $self->get_form()->inline_error_messages();
+	my $label = $self->get_item_label();
+	
+	if($err_val eq 'WebAppForm::NumberFieldErr_Range')
+	{
+		my $validation = $self->get_item_validation();
+		if($validation =~ m/\Arange\((-?[\d\.]*)\s*,\s*(-?[\d\.]*)\)\Z/)
+		{
+			return $label . " must be a number between $1 and $2 inclusive"
+				if $1 ne '' && $2 ne '';
+			return $label . " must be a number greater or equal to $1"
+				if $1 ne '';
+			return $label . " must be a number less than or equal to $2"
+				if $2 ne '';
+		}
+
+		return $label . " must be a valid number"
+	}
+	elsif($err_val eq 'WebAppForm::NumberFieldErr_Format')
+	{
+		return $inline_errs?'You entered some non-digit characters, please check'
+			:"You entered some non-digit characters in $label, please check";
+	}
+	elsif($err_val eq 'WebAppForm::NumberFieldErr_Blank')
+	{
+		return $inline_errs?'Please enter a number':$label . ' has been left blank';
+	}
+
+	'unknown error type'
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/SubmitButton.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/SubmitButton.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/SubmitButton.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,93 @@
+package WebAppFramework::Unit::FormItem::SubmitButton;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FormItem';
+use CppVariable;
+
+# new() parameters:
+#	Name => Form item name
+#	Text => Value text (will be translated)
+#	Report => if present, the clicked status of the button will be reported
+
+# To change the text of the label, put a Unit in the 'Label' position.
+# But be careful it only outputs text!
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# write the item
+		$output->write_text('<input type="submit" name="'.$self->get_id().'"');
+		if(exists ${$$self{'_units'}}{'Label'})
+		{
+			# write this unit as a label
+			my $label = ${$$self{'_units'}}{'Label'};
+			$output->write_text(' value="');
+			$label->write_unit($output,$phase,$subphase);
+			$output->write_text('"');
+		}
+		elsif(exists $$self{'Text'})
+		{
+			# output normal static text
+			$output->write_text(' value="');
+			$output->write_text_translated($$self{'Text'});
+			$output->write_text('"');
+		}
+		$output->write_text('>');
+	}
+	else
+	{
+		# write stuff for label unit, if it exists
+		if(exists ${$$self{'_units'}}{'Label'})
+		{
+			my $label = ${$$self{'_units'}}{'Label'};
+			$label->write_unit($output,$phase,$subphase);
+		}
+	}
+}
+
+# by default, no form variable is used for submit buttons
+# If the result should be reported, then the name followed by 'Clicked' is reported
+sub get_form_variables
+{
+	my ($self) = @_;
+
+	return () unless exists $$self{'Report'};
+
+	return (cppvar('bool', $$self{'Name'}.'Clicked'))
+}
+
+# and no acceptor code should be written, unless we're reporting the clickedness
+sub requires_acceptor_code()
+{
+	my ($self) = @_;
+	return exists $$self{'Report'};
+}
+
+# report acceptance?
+sub write_value_acceptance_code
+{
+	my ($self, $output, $data_source, $validity_error_name) = @_;
+	
+	return unless exists $$self{'Report'};
+	
+	$output->write_code('m'.$self->get_item_name()."Clicked = true;\n");
+	die "Internal logic error" unless $validity_error_name eq '';
+}
+
+# shouldn't ever display an error marker
+sub disable_error_marker
+{
+	return 1;
+}
+
+# doesn't do any validation
+sub always_passes_validation
+{
+	return 1
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/TextField.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/TextField.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem/TextField.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,286 @@
+package WebAppFramework::Unit::FormItem::TextField;
+use strict;
+use base 'WebAppFramework::Unit::FormItem';
+use WebAppFramework::Validator;
+use CppVariable;
+
+# new() parameters:
+#	Name => Form item name
+# 	Default => source of default value (can be CONSTANT:value for constant values)
+#	DisplayAsPassword => if present, then display the field as a password entry
+#	Size => size of field
+#	MaxLength => max size of entered text, if browser respects this
+#	BlankAllowed => if present, then a blank field is an acceptable value which passes validation
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# field size
+		my ($size,$size_y) = split /\s*,\s*/,$$self{'Size'};
+		$size = int($size);
+		$size_y = int($size_y);
+
+		# Generate attributes
+		my $attr = ' type="'.$self->_ftype().'" name="'.$self->get_id().'"';
+		$attr .= $self->get_tag_attributes();
+		$attr .= qq` size="$size"` if $size > 0;
+		my $maxlength = int($$self{'MaxLength'});
+		$attr .= qq` maxlength="$maxlength"` if $maxlength > 0;
+
+		# should we use a text area?
+		my $is_text_area = ($size != 0 && $size_y != 0);
+		if($is_text_area)
+		{
+			# redo the attributes
+			$attr = ' name="'.$self->get_id().'"';
+			$attr .= $self->get_tag_attributes();
+		}
+	
+		# write the item
+		if($subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_DEFAULT)
+		{
+			# Filter the default value (if any)
+			my $default = $self->filter_default_value($$self{'Default'}, 'std::string');
+		
+			# text area or normal?
+			if($is_text_area)
+			{
+				$output->write_text(qq!<textarea$attr cols="$size" rows="$size_y">!);
+				if(defined $default)
+				{
+					$self->write_variable_text($output, $default);
+				}
+				$output->write_text(qq!</textarea>!);
+			}
+			else
+			{
+				# normal field
+				$output->write_text("<input$attr");
+				# write a default value? (if it's a constant, it'll end up as static text)
+				if(defined $default)
+				{
+					$output->write_text(' value="');
+					$self->write_variable_text($output, $default);
+					$output->write_text('"');
+				}
+				$output->write_text('>');
+			}
+		}
+		else
+		{
+			if($is_text_area)
+			{
+				$output->write_text(qq!<textarea$attr cols="$size" rows="$size_y">!);
+				$self->write_variable_text($output, $self->get_value_page_variable());
+				$output->write_text(qq!</textarea>!);
+			}
+			else
+			{
+				# normal field
+				$output->write_text(qq`<input$attr value="`);
+				$self->write_variable_text($output, $self->get_value_page_variable());
+				$output->write_text('">');
+			}
+		}
+	}
+}
+
+# Field is always a string
+sub get_form_variable_type
+{
+	my ($self) = @_;
+	return ('std::string')
+}
+
+sub write_value_acceptance_code
+{
+	my ($self, $output, $data_source, $validity_error_name) = @_;
+	
+	# make sure validator is created (if applicable)
+	my $validator = $self->ensure_validator();
+
+	# validation method
+	my $validation = $self->get_item_validation();
+	
+	# data value within the form
+	my $dv = 'm'.$self->get_item_name();
+	
+	# store the data value
+	$output->write_code($dv .' = '.$data_source->convert_to('std::string'). ";\n");
+
+	if(exists $$self{'BlankAllowed'})
+	{
+		# make blank values allowed
+		$output->write_code("if($dv.empty())\n{\n$validity_error_name = WebAppForm::Valid;\n}\nelse\n{\n")
+	}
+
+	# do validation
+	if(defined $validator)
+	{
+		# use the supplied validator
+		$output->write_code("bool validatorResult = false;\n");
+		$output->write_code($validator->generate_code(cppvar('std::string', $dv), 'validatorResult'));
+		$output->write_code("$validity_error_name = validatorResult?(WebAppForm::Valid):(WebAppForm::NotValid);\n");
+	}
+	elsif($validation eq 'none')
+	{
+		# no validation, always is OK -- and there won't be any *Valid variable created, so do nothing
+	}
+	elsif($validation eq 'external')
+	{
+		# external validation -- always false
+		$output->write_code($validity_error_name . " = WebAppForm::NotValid;\n")
+	}
+	elsif($validation =~ m/\Alength\((\d*),(\d*)\)\Z/)
+	{
+		my ($min,$max) = ($1,$2);
+		die "TextField length(min,max) specified, but both range ends are unspecified" if $min eq '' && $max eq '';
+			
+		$output->write_code($validity_error_name . ' = ('. join(' && ',
+			($min ne '')?('('.$dv.".size() >= $min)"):(),
+			($max ne '')?('('.$dv.".size() <= $max)"):(),
+			) . ")?(WebAppForm::Valid):(WebAppForm::NotValid);\n")
+	}
+	else
+	{
+		die "Unknown TextField validation method $validation"
+	}
+
+	if(exists $$self{'BlankAllowed'})
+	{
+		# end the if clause to make blank values allowed
+		$output->write_code("}\n")
+	}
+}
+
+# type of field -- allows selection of password display
+sub _ftype
+{
+	my ($self) = @_;
+	return (exists $$self{'DisplayAsPassword'})?"password":"text"
+}
+
+
+# will need some extra headers if doing email validation
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+	
+	my @h;
+
+	# make sure validator is created (if applicable)
+	my $validator = $self->ensure_validator();
+
+	if(defined $validator)
+	{
+		# Standard headers required
+		if($type == WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)
+		{
+			push @h,$validator->get_required_headers_this(0) # false, project headers
+		}
+		elsif($type == WebAppFramework::Unit::HEADERS_PAGE_H_SYSTEM)
+		{
+			push @h,$validator->get_required_headers_this(1) # true, system headers
+		}
+	}
+
+	@h
+}
+
+
+
+# make a default error message
+sub make_validation_fail_message
+{
+	my ($self) = @_;
+	
+	# make sure validator is created (if applicable)
+	my $validator = $self->ensure_validator();
+
+	my $validation = $self->get_item_validation();
+
+	if(defined $validator)
+	{
+		return $validator->generate_default_error_message( $self->get_item_label())
+	}
+	elsif($validation eq 'none' || $validation eq 'external')
+	{
+		# no error message for these cases -- provided externally
+		return '';
+	}
+	elsif($validation =~ m/\Alength\((\d*),(\d*)\)\Z/)
+	{
+		my ($min,$max) = ($1,$2);
+
+		my $m = $self->get_item_label() . ' must be ';
+		if($min ne '' && $max ne '')
+		{
+			$m .= "between $min and $max"
+		}
+		elsif($min ne '')
+		{
+			$m .= "at least $min"
+		}
+		else
+		{
+			$m .= "no more than $max"
+		}
+		$m .= ' characters long';
+		
+		return $m
+	}
+
+	die "Unknown TextField validation method $validation"
+}
+
+# some methods don't do validation
+sub always_passes_validation
+{
+	my ($self) = @_;
+	
+	my $validation = $self->get_item_validation();
+
+	return ($validation eq 'none')
+}
+
+
+# make the sure the validator object (if appropraite) is loaded
+sub ensure_validator
+{
+	my ($self) = @_;
+
+	unless(exists $$self{'_validator'})
+	{
+		# always make it _something_
+		$$self{'_validator'} = undef;
+
+		# get validation using base class		
+		my $validation = $self->get_item_validation();
+		
+		# is it a reference?
+		if(ref($validation))
+		{
+			# user supplied validator
+			$$self{'_validator'} = $validation;
+		}
+		else
+		{
+			# is the validation specified a builtin type?
+			$validation =~ /\A([^\(]*)\(?/;
+			my $vn = $1;
+			if($vn ne 'none' && $vn ne 'external' && $vn ne 'length')
+			{
+				# it's a validator... load it (will terminate the process if it's not found)
+				$$self{'_validator'} = WebAppFramework::Validator::load_builtin($validation)
+			}
+		}
+		# otherwise, remains as undef
+	}
+	
+	return $$self{'_validator'}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormItem.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,269 @@
+package WebAppFramework::Unit::FormItem;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+use CppVariable;
+use WebAppFramework::Unit::Form;
+
+# new() parameters:
+#	Name => Form item name
+#	Label => Label, subject to translation. Used for automatically generating error messages
+#	Validation => Validation information
+#	ValidationFailMsg => Text to display when validation fails
+#	Attributes => Extra tags for items, eg 'class="css_style"'
+
+# override from base class
+sub is_form_item
+{
+	return 1;
+}
+
+# most forms will just require one ID
+sub get_num_ids_required
+{
+	return 1;
+}
+
+# called by the form with the required number of IDs as argument
+sub set_ids
+{
+	my ($self, @ids) = @_;
+	$$self{'_item_ids'} = [@ids];
+}
+
+# get entire set of IDs
+sub get_ids
+{
+	my ($self) = @_;
+	@{$$self{'_item_ids'}};
+}
+
+# in the case where there's just one ID, return the single ID
+sub get_id
+{
+	my ($self) = @_;
+	die "Form item ".$self->get_item_name()." has more than one ID" unless $self->get_num_ids_required() == 1;
+	${$$self{'_item_ids'}}[0];
+}
+
+# return name of item
+sub get_item_name
+{
+	my ($self) = @_;
+	die "Form item has no name" unless exists $$self{'Name'};
+	$$self{'Name'}
+}
+
+# get the label, abort if not present
+sub get_item_label
+{
+	my ($self) = @_;
+	die "Form item $$self{'Name'} (type ".ref($self).") has no label" unless exists $$self{'Label'};
+	$$self{'Label'}
+}
+
+# get item validation string
+sub get_item_validation
+{
+	my ($self) = @_;
+	die "Form item $$self{'Name'} (type ".ref($self).") has no validation info" unless exists $$self{'Validation'};
+	$$self{'Validation'}
+}
+
+
+# return a list of the variables used to return results
+# The derived class must support get_form_variable_type(), which
+# returns the C++ type of the variable.
+sub get_form_variables
+{
+	my ($self) = @_;
+	return (cppvar($self->get_form_variable_type(), $$self{'Name'}))
+}
+
+# whether or not this item requires form variable acceptor code
+sub requires_acceptor_code()
+{
+	my ($self) = @_;
+	return 1;
+}
+
+# return the form this item belongs in
+sub get_form
+{
+	my ($self) = @_;
+	return $self->get_parent_of_type('WebAppFramework::Unit::Form');
+}
+
+# Return a string containing any extra attributes for the class.
+# First character is a space, for neat additions. Or the empty string if there are none.
+sub get_tag_attributes
+{
+	my ($self) = @_;
+	my $a = $$self{'Attributes'};
+	$a =~ s/\s+/ /g;
+	$a =~ s/\A\s+//;
+	$a =~ s/\s+\Z//;
+	($a eq '')?'':' '.$a
+}
+
+# Returns a page variable name referencing the current value entered by the user
+sub get_value_page_variable
+{
+	my ($self) = @_;
+
+	my @v = $self->get_form_variables();
+	die "Item has more than one form variable, can't use get_value_page_variable()"
+		unless $#v == 0;
+
+	$self->get_form()->get_form_name().'.'.$v[0]->name();
+}
+
+# disable display of the error marker?
+sub disable_error_marker
+{
+	return 0;
+}
+
+# override writing to handle writing error markers
+sub write
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# ask base class to do the magic
+	$self->SUPER::write($output, $phase, $subphase);
+	
+	# only want to write the error marker in specific phase...
+	return unless $phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT
+		&& $subphase == WebAppFramework::Unit::Form::OUTPUT_SUB_PHASE_REDISPLAY;
+
+	# is writing of error markers disabled?
+	return if $self->disable_error_marker();
+	
+	# find the form
+	my $form = $self->get_form();
+	
+	# get an error marker from somewhere
+	my $error_marker = $self->find_form_template_fragment('ErrorMarker');
+	if($self->always_passes_validation() || ($error_marker eq '' && !($form->inline_error_messages())))
+	{
+		# don't do anything if the null string is returned and error messages aren't inline
+		# or if the field always passes validation
+		return;
+	}
+	
+	# ask the form what the expression is called
+	my $expression = $form->get_is_item_valid_expression($self);
+	
+	# and write some code!
+	$output->write_code("if(!$expression)\n{\n");
+	$output->write_text($error_marker);
+	if($form->inline_error_messages())
+	{
+		# write error message surrounded by fragments
+		$output->write_text($self->find_form_template_fragment('InlineErrorStart'));
+		$self->write_validation_fail_message($output);
+		$output->write_text($self->find_form_template_fragment('InlineErrorEnd'));
+	}
+	$output->write_code("}\n");
+}
+
+# find an html fragment from the form or a container subclass.
+# Should not be subjected to translation.
+sub find_form_template_fragment
+{
+	my ($self, $frag_name) = @_;
+	
+	# Find a parent which can provide this -- search for a method
+	# called get_form_template_fragment
+	my $r = $self;
+	while(exists $$r{'_parent'})
+	{
+		my $f = $$r{'_parent'}->get_form_template_fragment($frag_name);
+		return $f if defined $f;
+		$r = $$r{'_parent'};
+	}
+
+	die "FormItem::find_form_template_fragment could not locate an object implementing the required method"
+}
+
+
+# write the validation failed message
+sub write_validation_fail_message
+{
+	my ($self, $output) = @_;
+	
+	if(exists $$self{'ValidationFailMsg'})
+	{
+		$output->write_text_translated($$self{'ValidationFailMsg'})
+	}
+	else
+	{
+		# How many possible messages are there?
+		my @msg_vals = $self->get_values_of_validation_fail_messages();
+		if($#msg_vals < 0)
+		{
+			# only only one message, just write that one
+			$output->write_text_translated($self->make_validation_fail_message())
+		}
+		else
+		{
+			my $v = $self->get_form()->get_item_validity_error($self);
+			# write a message for possible value
+			for(@msg_vals)
+			{
+				my @vs = split /\|/,$_;
+				$output->write_code("if(".join(' || ',map {"$v == $_"} @vs).")\n{\n");
+				$output->write_text_translated($self->make_validation_fail_message($vs[0]));
+				$output->write_code("}\n");
+			}
+		}
+	}
+}
+
+# The constant values of possible validation failure messages
+# Or the empty string, if there's just one
+sub get_values_of_validation_fail_messages
+{
+	return ();
+}
+
+# doesn't always pass validation
+sub always_passes_validation
+{
+	return 0
+}
+
+# what's the default state for the validation state variable?
+sub default_validation_state
+{
+	return 'WebAppForm::NotValid'
+}
+
+# Filtering of default values
+# This must be called by the FormItem to filter the default value.
+# Arguments:
+#   default value, as supplied by the user
+#	C++ type
+# Returns a new default value.
+sub filter_default_value
+{
+	my ($self,$default_value,$cpp_type) = @_;
+	if(exists $$self{'__default_value_filter'})
+	{
+		return &{$$self{'__default_value_filter'}}(@_);
+	}
+	return $default_value
+}
+
+# Set the default filter on this form item.
+# Returns the old filter, if one was set.
+sub set_default_value_filter
+{
+	my ($self,$filter) = @_;
+	my $old = $$self{'__default_value_filter'};
+	$$self{'__default_value_filter'} = $filter;
+	$old
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormTableContainer.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormTableContainer.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FormTableContainer.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,133 @@
+package WebAppFramework::Unit::FormTableContainer;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::TableContainer';
+
+use WebAppFramework::Unit::FormItem::TextField;
+use WebAppFramework::Unit::FormItem::NumberField;
+use WebAppFramework::Unit::TranslatedText;
+use WebAppFramework::Unit::FormItem::SubmitButton;
+use WebAppFramework::Unit::FormItem::Checkbox;
+use WebAppFramework::Unit::FormItem::Choice;
+
+# new() parameters:
+#	Template => base name of template filename
+#   FragmentsName => name of fragments to pull out of the file
+
+sub get_required_fragments
+{
+	my ($self) = @_;
+	return ($self->SUPER::get_required_fragments(), qw/ErrorMarker/);
+}
+
+# add a text field
+sub add_text_field
+{
+	my ($self, $name, $label, $validation, @specification) = @_;
+	
+	my @a;
+	push @a,'Label' => $label if $label ne '';
+	push @a,'Validation' => $validation if $validation ne '';
+
+	my $unit = WebAppFramework::Unit::FormItem::TextField->new('Name' => $name, @a, @specification);
+	
+	$self->_insert_field($unit, $label);
+	
+	$unit
+}
+
+# Add a number field
+sub add_number_field
+{
+	my ($self, $name, $label, $validation, $default_number, $blank_value, @specification) = @_;
+	
+	my @a;
+	push @a,'Label' => $label if $label ne '';
+	push @a,'Validation' => $validation if $validation ne '';
+	push @a,'BlankValue' => $blank_value if $blank_value ne '';
+
+	my $unit = WebAppFramework::Unit::FormItem::NumberField->new('Name' => $name, 'DefaultNumber' => $default_number, @a, @specification);
+	
+	$self->_insert_field($unit, $label);
+	
+	$unit
+}
+
+# Add a submit button
+sub add_submit_button
+{
+	my ($self, $name, $text, @specification) = @_;
+
+	my $unit = WebAppFramework::Unit::FormItem::SubmitButton->new('Name' => $name, 'Text' => $text, @specification);
+
+	$self->_insert_field($unit, '');
+	
+	$unit
+}
+
+# Add a checkbox
+sub add_checkbox
+{
+	my ($self, $name, $label, @specification) = @_;
+
+	my $unit = WebAppFramework::Unit::FormItem::Checkbox->new('Name' => $name, 'Label' => $label, @specification);
+
+	$self->_insert_field($unit, '');
+	
+	$unit
+}
+
+# Add a choices field
+sub add_choice
+{
+	my ($self, $name, $label, $choices, $style, $validation, $default, @specification) = @_;
+	
+	my @a;
+	push @a,'Default' => $default if $default ne '';
+	push @a,'Choices' => $choices unless ref($choices);
+
+	my $unit = WebAppFramework::Unit::FormItem::Choice->new('Name' => $name, 'Label' => $label,
+		'Style' => $style, 'Validation' => $validation, @a, @specification);
+
+	$self->_insert_field($unit, $label);
+	
+	# if choices is a data source, add it as a sub-unit
+	$unit->add_unit('DataSource', $choices) if ref($choices);
+	
+	$unit
+}
+
+# add an arbitary item
+sub add_item
+{
+	my ($self, $name, $label, $unit) = @_;
+	$self->_insert_field($unit, $label);
+	
+	$unit
+}
+
+sub _insert_field
+{
+	my ($self, $unit, $label) = @_;
+
+	# find the first unused position
+	my $p = 0;
+	while(exists ${$$self{_units}}{"1_$p"} || exists ${$$self{_units}}{"0_${p}_Label"})
+	{
+		$p++;
+	}
+	
+	# insert into table
+	$self->add_unit("1_$p", $unit);
+	$self->add_unit("0_${p}_Label", WebAppFramework::Unit::TranslatedText->new('Text' => $label))
+		if $label ne '';
+}
+
+# allow markers and other fragements to be overriddden
+sub get_form_template_fragment
+{
+	my ($self,$frag_name) = @_;
+	return $self->get_fragment($frag_name);
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FragmentsTemplate.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FragmentsTemplate.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/FragmentsTemplate.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,99 @@
+package WebAppFramework::Unit::FragmentsTemplate;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::Templated';
+
+# new() parameters:
+#	Template => base name of template filename
+#   FragmentsName => name of fragments to pull out of the file
+
+# Derived objects must implement get_required_fragments(), which
+# returns a list of all the fragment names this object must support.
+
+# $$self{'_fragments'} is a ref to a hash array of fragment text.
+
+sub process_template
+{
+	my ($self, $template_r) = @_;
+
+	my $fragments = {};
+	my $frag_name = $$self{'FragmentsName'};
+
+	# proces the template, storing as text sections separated by insert point names
+	while($$template_r =~ m`<!--$frag_name-(.+?)-->(.*?)<!--$frag_name/-->`gs)
+	{
+		my ($frag_name, $frag_value) = ($1,$2);
+
+		# clean up leading and trailing whitespace
+		$frag_value =~ s/\A\s+//;
+		$frag_value =~ s/\s+\Z//;
+
+		# store
+		$$fragments{$frag_name} = $frag_value
+	}
+	
+	# check there are the right fragments
+	my @required_fragments = $self->get_required_fragments();
+	for(@required_fragments)
+	{
+		unless(exists $$fragments{$_})
+		{
+			die "Fragment $frag_name-$_ does not exist in template"
+		}
+	}
+	
+	# store fragments in object
+	$$self{'_fragments'} = $fragments;
+}
+
+# writes a bit of fragment text, but only if it's in the output writing phase
+sub write_fragment_text
+{
+	my ($self, $output, $phase, $subphase, $frag_name) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		die "Fragment $frag_name not found" unless exists ${$$self{'_fragments'}}{$frag_name};
+
+		# HTML fragement, so translate within those markers!
+		$output->write_text_translate_within_markers(${$$self{'_fragments'}}{$frag_name});
+	}
+}
+
+sub check_fragment_exists
+{
+	my ($self, $frag_name) = @_;
+	
+	unless(exists ${$$self{'_fragments'}}{$frag_name})
+	{
+		die "Fragment ".$$self{'FragmentsName'}."-$frag_name does not exist in template"
+	}
+}
+
+sub get_fragment
+{
+	my ($self, $frag_name) = @_;
+	unless(exists ${$$self{'_fragments'}}{$frag_name})
+	{
+		die "Fragment ".$$self{'FragmentsName'}."-$frag_name does not exist in template"
+	}
+	# translate within markers and return
+	my $t = ${$$self{'_fragments'}}{$frag_name};
+	{
+		# not the most pleasant way of doing this
+		my $output = WebAppFramework::Output::find_output_object();
+		if(defined $output)
+		{
+			# ask the output object to translate text within the markers
+			$output->translate_within_markers(\$t);
+		}
+		else
+		{
+			die "WebAppFramework::Unit::FragmentsTemplate -- no output unit can be found when this get_fragment called. Use write_fragement_text instead."
+		}
+	}
+	return $t;
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/IncludeOnPages.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/IncludeOnPages.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/IncludeOnPages.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,60 @@
+package WebAppFramework::Unit::IncludeOnPages;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit which only outputs it's sub units on specific pages
+# (on other pages, the sub-units are actually deleted)
+
+# new() parameters:
+#	Pages => list of pages the sub units should be included on
+#	NotPages => list of pages the sub units should not be included on
+# one or the other can be used, but not both
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# need to delete the units?
+		my $delete_subunits = 1;
+		my $current_page = $self->get_pagename();
+		
+		# parse parameters
+		if(exists $$self{'Pages'} && exists $$self{'NotPages'})
+		{
+			die "WebAppFramework::Unit::IncludeOnPages has both Pages and NotPages specified. Not allowed!";
+		}
+		if(exists $$self{'Pages'})
+		{
+			for($self->list_to_array($$self{'Pages'}))
+			{
+				$delete_subunits = 0 if $current_page eq $_
+			}
+		}
+		elsif(exists $$self{'NotPages'})
+		{
+			$delete_subunits = 0;
+			for($self->list_to_array($$self{'NotPages'}))
+			{
+				$delete_subunits = 1 if $current_page eq $_
+			}
+		}
+		else
+		{
+			die "WebAppFramework::Unit::IncludeOnPages does not have Pages or NotPages parameter specified.";
+		}
+		
+		# delete any...
+		if($delete_subunits)
+		{
+			$$self{'_units'} = {}
+		}
+	}
+	
+	# call base class
+	WebAppFramework::Unit::write_unit(@_);
+}
+
+1;
\ No newline at end of file

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/LinkToPage.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/LinkToPage.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/LinkToPage.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,36 @@
+package WebAppFramework::Unit::LinkToPage;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit to output a <a href="" ...> link
+
+# new() parameters:
+#	Link => array of arguments to the write link call, ie, [$pagename, @parameters]
+#   * => any other attributes which should be output
+# 		(eg 'class' => 'large' for CSS class, outputs class="large" as attribute)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		$output->write_text('<a href="');
+		$self->write_page_address($output, @{$$self{'Link'}});
+		
+		# build list of other things
+		my @rest = ('"');
+		while(my ($k,$v) = each %$self)
+		{
+			if($k ne 'Link' && $k !~ m/\A_/)
+			{
+				push @rest,qq`$k="$v"`;
+			}
+		}
+		$output->write_text(join(' ',@rest));
+		$output->write_text('>');
+	}
+}
+
+1;
\ No newline at end of file

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ListOfLinks.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ListOfLinks.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/ListOfLinks.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,52 @@
+package WebAppFramework::Unit::ListOfLinks;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit to output a <a href="" ...>TRANSLATED_TEXT</a> ... links
+
+# new() parameters:
+#	Links => Array of link, as [['Translate this', [linkspec]], ...]
+#	Separator => raw HTML separator, if ' ' not acceptable (optional)
+#   * => any other attributes which should be output
+# 		(eg 'class' => 'large' for CSS class, outputs class="large" as attribute)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# build list of attributes
+		my @rest = ('"');
+		while(my ($k,$v) = each %$self)
+		{
+			if($k ne 'Links' && $k ne 'Separator' && $k !~ m/\A_/)
+			{
+				push @rest,qq`$k="$v"`;
+			}
+		}
+		my $terminate_link = join(' ',@rest) . '>';
+		
+		# what separator?
+		my $separator = (exists $$self{'Separator'})?$$self{'Separator'}:' ';
+				
+		# write out each link in turn
+		my $first = 1;
+		for(@{$$self{'Links'}})
+		{
+			my ($text,$link) = @$_;
+			# separate it
+			$output->write_text($separator) unless $first;
+			$first = 0;
+			# build up the link
+			$output->write_text('<a href="');
+			$self->write_page_address($output, @$link);
+			$output->write_text($terminate_link);
+			$output->write_text_translated($text);
+			$output->write_text('</a>');
+		}
+	}
+}
+
+1;
\ No newline at end of file

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/Date.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/Date.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/Date.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,41 @@
+package WebAppFramework::Unit::Localised::Date;
+use strict;
+use base 'WebAppFramework::Unit';
+use WebAppFramework::DateAndTime;
+
+# Unit to output a date via the locale mechanism
+
+# new() parameters:
+#	Date => standard date specification
+#	Style => Long or Short (defaults to Long)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# style of date output?
+		my $style = $$self{'Style'};
+		$style = 'Long' unless $style ne '';
+		
+		# Get parameters
+		my $params = WebAppFramework::DateAndTime::make_YMD_params(
+				$self,
+				$self->list_to_array($$self{'Date'})
+			);
+	
+		# write the code!
+		$output->write_code(<<__E);
+			{
+				std::string formattedDate;
+				locale.FormatDate(WAFLocale::DateFormat$style, $params, formattedDate);
+				rResponse.Write(formattedDate.c_str(), formattedDate.size());
+			}
+__E
+	}
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/DateTime.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/DateTime.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Localised/DateTime.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,45 @@
+package WebAppFramework::Unit::Localised::DateTime;
+use strict;
+use base 'WebAppFramework::Unit';
+use WebAppFramework::DateAndTime;
+
+# Unit to output a date via the locale mechanism
+
+# new() parameters:
+#	DateTime => Page variable
+#	InFormat => YYYYMMDD or UNIXEpoch (defaults to UNIXEpoch if not specified)
+#	Style => Any format specified in WAFLocale.h (defaults to DateTimeFormatLong if not specified)
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		my $source = $self->get_variable($$self{'DateTime'})->convert_to('int32_t');
+		my $style = 'DateTimeFormatLong';
+		if(exists $$self{'Style'})
+		{
+			$style = $$self{'Style'}
+		}
+		my $informat = 'TimeFormatUNIXEpoch';
+		if(exists $$self{'InFormat'})
+		{
+			$informat = 'TimeFormat'.$$self{'InFormat'}
+		}
+	
+		# write the code!
+		$output->write_code(<<__E);
+			{
+				int32_t dateTime = $source;
+				std::string formattedDate;
+				locale.FormatDateTime(WAFLocale::$style, WAFLocale::$informat, dateTime, formattedDate);
+				rResponse.Write(formattedDate.c_str(), formattedDate.size());
+			}
+__E
+	}
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Menu.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Menu.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Menu.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,100 @@
+package WebAppFramework::Unit::Menu;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FragmentsTemplate';
+
+# Unit to output a simple menu
+
+# new() parameters:
+#	Items => array of items, each of which is anonymous array ['Name (translated)', [link spec], 'optional C++ condition for addition "this page" check']
+#	DifferentOnThisPage => if set, use the alternative ItemThisPage fragment when we're on the page we're linking to
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# Make sure the template has been loaded
+	$self->ensure_template_loaded($output);
+	
+	# Don't link on page flag
+	my $different_on_this_page = exists $$self{'DifferentOnThisPage'};
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# Write menu header
+		$self->write_fragment_text($output, $phase, $subphase, 'Begin');
+
+		# Write items
+		for(@{$$self{'Items'}})
+		{
+			my ($text,$link,$condition) = @$_;
+			
+			if($different_on_this_page && ($self->get_pagename() eq $$link[0]))
+			{
+				# entry is on the page it's linking to
+				if($condition eq '')
+				{
+					# no condition for this, just write the template out
+					my $template = $self->get_fragment('ItemThisPage');
+					$self->write_menu_entry($output,$template,$text,$link);
+				}
+				else
+				{
+					# write both possibilities
+					$output->write_code('if('.$condition.")\n{\n");
+					# write template for this page
+					{
+						my $template = $self->get_fragment('ItemThisPage');
+						$self->write_menu_entry($output,$template,$text,$link);
+					}
+					$output->write_code("}\nelse\n{\n");
+					# write template for normal entry
+					{
+						my $template = $self->get_fragment('Item');
+						$self->write_menu_entry($output,$template,$text,$link);
+					}
+					$output->write_code("}\n");
+				}
+			}
+			else
+			{
+				# always the same, or not on the right page
+				my $template = $self->get_fragment('Item');
+				$self->write_menu_entry($output,$template,$text,$link);
+			}
+		}
+
+		# write footer
+		$self->write_fragment_text($output, $phase, $subphase, 'End');		
+	}
+}
+
+sub write_menu_entry
+{
+	my ($self,$output,$template,$text,$link) = @_;
+	for(split /\[(URL|TEXT)\]/,$template)
+	{
+		if($_ eq 'URL')
+		{
+			$self->write_page_address($output, @$link);
+		}
+		elsif($_ eq 'TEXT')
+		{
+			$output->write_text_translated($text);
+		}
+		else
+		{
+			$output->write_text($_)
+		}
+	}
+}
+
+sub get_required_fragments
+{
+	my ($self) = @_;
+	my @r = qw/Begin Item End/;
+	push @r,'ItemThisPage' if exists $$self{'DifferentOnThisPage'};
+	@r
+}
+
+1;
\ No newline at end of file

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/OutputIf.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/OutputIf.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/OutputIf.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,70 @@
+package WebAppFramework::Unit::OutputIf;
+use strict;
+use base 'WebAppFramework::Unit';
+use base 'Exporter';
+use vars qw/@EXPORT/;
+@EXPORT = qw(output_if);
+
+# new() parameters:
+#	Condition => Condition to evaluate.
+#
+# Place Units to be output when this is true at 'true', false at 'false'.
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	if($phase == WebAppFramework::Unit::PHASE_INITIALISE)
+	{
+		# check names of positions
+		for(keys %{$$self{'_units'}})
+		{
+			if($_ ne 'true' && $_ ne 'false')
+			{
+				die "Bad position '$_' in OutputIf unit"
+			}
+		}
+	}
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# output statement
+		$output->write_code('if('.$$self{'Condition'}.")\n{\n");
+	}
+	
+	# write true unit?
+	if(exists ${$$self{'_units'}}{'true'})
+	{
+		${$$self{'_units'}}{'true'}->write($output, $phase, $subphase);
+	}
+	# write false unit?
+	if(exists ${$$self{'_units'}}{'false'})
+	{
+		if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+		{
+			$output->write_code("}\nelse\n{\n");
+		}
+		${$$self{'_units'}}{'false'}->write($output, $phase, $subphase);
+	}
+		
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# finish statement
+		$output->write_code("}\n");
+	}
+}
+
+# helper function, which returns a suitable output if without the hassle
+# of writing out the constructor properly.
+sub output_if
+{
+	my ($condition, $if_true, $if_false) = @_;
+	
+	my $unit = WebAppFramework::Unit::OutputIf->new('Condition' => $condition);
+	$unit->add_unit('true', $if_true) if defined $if_true;
+	$unit->add_unit('false', $if_false) if defined $if_false;
+	$unit
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/PageTemplate.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/PageTemplate.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/PageTemplate.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,18 @@
+package WebAppFramework::Unit::PageTemplate;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::TemplatedInsert';
+
+# new() parameters:
+#	Template => base name of template filename
+
+sub remove_unwanted_text
+{
+	my ($self, $template_r) = @_;
+	
+	# remove bits of the page we're not supposed to include
+	$$template_r =~ s/<!--PageTemplate-Omit-Begin-->(.+?)<!--PageTemplate-Omit-End-->//gs;
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/RawHTML.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/RawHTML.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/RawHTML.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,22 @@
+package WebAppFramework::Unit::RawHTML;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit to output untranslated HTML
+
+# new() parameters:
+#	HTML => raw HTML to output
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# write the HTML, interpolating in variables
+		$self->write_interpolated_text($output, $$self{'HTML'});
+	}
+}
+
+1;
\ No newline at end of file

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SectionTemplate.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SectionTemplate.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SectionTemplate.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,25 @@
+package WebAppFramework::Unit::SectionTemplate;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::TemplatedInsert';
+
+# new() parameters:
+#	Template => base name of template filename
+#	Marker => Text used in HTML comments to mark the bit of text used
+
+sub remove_unwanted_text
+{
+	my ($self, $template_r) = @_;
+	
+	# remove bits of the page we're not supposed to include
+	my $marker = $$self{'Marker'};
+	die "No Marker specified for SectionTemplate" unless $marker ne '';
+	unless($$template_r =~ m/<!--\s*$marker\s*-->(.+?)<!--\s*$marker\s*-->/s)
+	{
+		die "Marker $marker not found in HTML comments in SectionTemplate"
+	}
+	$$template_r = $1;
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SimpleContainer.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SimpleContainer.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/SimpleContainer.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,65 @@
+package WebAppFramework::Unit::SimpleContainer;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+use base 'Exporter';
+use vars qw/@EXPORT/;
+@EXPORT = qw(join_units);
+use WebAppFramework::Unit::TranslatedText;
+
+# Simple container unit, simply outputs the sub units in sort() order of their positions.
+# Optionally separating them by an HTML fragment
+
+# new parameters:
+#	Separator => some HTML to separate the sub-units
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	my @u = sort keys %{$$self{'_units'}};
+	
+	for(my $i = 0; $i <= $#u; $i++)
+	{
+		# sub unit
+		${$$self{'_units'}}{$u[$i]}->write($output, $phase, $subphase);
+		
+		# separator
+		if($i != $#u
+			&& $phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT
+			&& exists $$self{'Separator'})
+		{
+			$output->write_text($$self{'Separator'})
+		}
+	}
+}
+
+# helper function. First param is Separator, then others are either references
+# to units, or text scalars which will be translated. Returns a filled
+# SimpleContainer unit to use.
+sub join_units
+{
+	my ($separator, @items) = @_;
+	
+	my $r = WebAppFramework::Unit::SimpleContainer->new('Separator' => $separator);
+	my $pos = 0;
+	for(@items)
+	{
+		my $loc = sprintf("%03d",$pos++);
+		if(ref($_))
+		{
+			# Reference, so assume it's a unit
+			$r->add_unit($loc,$_);
+		}
+		else
+		{
+			# Not a reference, so scalar text... add it translated
+			$r->add_unit($loc, WebAppFramework::Unit::TranslatedText->new('Text' => $_));
+		}
+	}
+	$r
+}
+
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TableContainer.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TableContainer.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TableContainer.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,103 @@
+package WebAppFramework::Unit::TableContainer;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::FragmentsTemplate';
+
+# new() parameters:
+#	Template => base name of template filename
+#   FragmentsName => name of fragments to pull out of the file
+
+# Add units with names <x>_<y> to position elements.
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# Make sure the template has been loaded
+	$self->ensure_template_loaded($output);
+
+	# Assemble into the nice grid layout
+	$self->ensure_assembled();
+	my $grid = $$self{'_assembly'};
+	my $grid_types = $$self{'_assembly_types'};
+
+	# Output units in order
+	$self->write_fragment_text($output, $phase, $subphase, 'Begin');
+	for(my $y = 0; $y <= $$self{'_max_y'}; $y++)
+	{
+		$self->write_fragment_text($output, $phase, $subphase, 'RowBegin');
+		for(my $x = 0; $x <= $$self{'_max_x'}; $x++)
+		{
+			# Write unit!
+			if($$grid[$x][$y] ne '')
+			{
+				$self->write_fragment_text($output, $phase, $subphase, 'CellBegin'.$$grid_types[$x][$y]);
+				
+				$$grid[$x][$y]->write($output, $phase, $subphase);
+				
+				$self->write_fragment_text($output, $phase, $subphase, 'CellEnd'.$$grid_types[$x][$y]);
+			}
+			else
+			{
+				# write an empty cell
+				$self->write_fragment_text($output, $phase, $subphase, 'EmptyCell');
+			}
+		}
+		$self->write_fragment_text($output, $phase, $subphase, 'RowEnd');
+	}
+	$self->write_fragment_text($output, $phase, $subphase, 'End');
+}
+
+sub get_required_fragments
+{
+	return qw/Begin RowBegin CellBegin CellEnd RowEnd EmptyCell End/;
+}
+
+sub ensure_assembled
+{
+	my ($self) = @_;
+
+	# already done the work?
+	return if exists $$self{'_assembly'};
+
+	# Set up vars
+	my $max_x = 0;
+	my $max_y = 0;
+	my $grid = [[]];
+	my $grid_types = [[]];
+	
+	# process the stored units, one by one
+	while(my ($position,$unit) = each %{$$self{'_units'}})
+	{
+		# match position to find x and y location
+		unless($position =~ m/\A(\d+)_(\d+)(|_(\w+))\Z/)
+		{
+			die "Invalid position $position in TableContainer"
+		}
+		my ($x,$y,$type) = ($1,$2,$4);
+		
+		# store in grid
+		$$grid[$x][$y] = $unit;
+		
+		# handle it being a different type to the default
+		if($type ne '')
+		{
+			$$grid_types[$x][$y] = '-'.$type;
+			$self->check_fragment_exists('CellBegin-'.$type);
+			$self->check_fragment_exists('CellEnd-'.$type);
+		}
+		
+		# record max position
+		$max_x = $x if $x > $max_x;
+		$max_y = $y if $y > $max_y;
+	}
+	
+	# store
+	$$self{'_max_x'} = $max_x;
+	$$self{'_max_y'} = $max_y;
+	$$self{'_assembly'} = $grid;
+	$$self{'_assembly_types'} = $grid_types;
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Templated.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Templated.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Templated.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,49 @@
+package WebAppFramework::Unit::Templated;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# new() parameters:
+#	Template => base name of template filename
+
+# In a function, call ensure_loaded to open the template.
+# This will call process_template with a reference to a scalar
+# containing the contents of the template file.
+sub ensure_template_loaded
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	my $language = $output->get_language();
+	if($language ne $$self{'_current_language'})
+	{
+		# not the last language used... wipe the template out
+		delete $$self{'_template'};
+		$$self{'_current_language'} = $language;
+	}
+
+	# Is the template loaded yet?
+	unless(exists $$self{'_template'})
+	{
+		# load it in from the file
+		my $filename = 'Templates/'.$$self{'Template'}.'.'.$language.'.html';
+		if(!-e $filename)
+		{
+			$filename = 'Templates/'.$$self{'Template'}.'.'.$output->get_default_language().'.html';
+		}
+		if(!-e $filename)
+		{
+			die "Cannot find template ".$$self{'Template'}.", for language $language, even when trying the default language ".$output->get_default_language()
+		}
+		# slurp in the entire file
+		my $t;
+		my $f = gensym;
+		open $f,$filename;
+		read $f,$t,-s $filename;
+		close $f;
+		
+		# ask the derived class to process the template
+		$self->process_template(\$t);
+	}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TemplatedInsert.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TemplatedInsert.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TemplatedInsert.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,62 @@
+package WebAppFramework::Unit::TemplatedInsert;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit::Templated';
+
+# new() parameters:
+#	Template => base name of template filename
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# Make sure the template has been loaded
+	$self->ensure_template_loaded($output);
+	
+	# need to output anything in this phase?
+	my $is_text = 1;
+	for(@{$$self{'_template'}})
+	{
+		if($is_text)
+		{
+			if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+			{
+				# This is templated HTML, so translate within the markers
+				$output->write_text_translate_within_markers($_);
+			}
+		}
+		else
+		{
+			# ask the unit to do something
+			if(exists ${$$self{'_units'}}{$_})
+			{
+				${$$self{'_units'}}{$_}->write($output, $phase, $subphase);
+			}
+			else
+			{
+				unless($self->is_global_phase($phase) || $phase == WebAppFramework::Unit::PHASE_INITIALISE)
+				{
+					# only print this warning when not doing the global phases, which use incomplete pages
+					print "WARNING: Unit '$_' has not been added in ".ref($self).", template=",$$self{'Template'},"\n"
+				}
+			}
+		}
+		
+		# toggle flag
+		$is_text = !$is_text;
+	}
+}
+
+sub process_template
+{
+	my ($self, $template_r) = @_;
+	
+	# remove bits of the page we're not supposed to include
+	$self->remove_unwanted_text($template_r);
+
+	# process the template, storing as text sections separated by insert point names
+	$$self{'_template'} = [split /###(.+?)###/,$$template_r];
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TranslatedText.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TranslatedText.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/TranslatedText.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,22 @@
+package WebAppFramework::Unit::TranslatedText;
+use strict;
+use base 'WebAppFramework::Unit';
+
+# new() parameters:
+#	Text => text to output, after translation
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# translate the text
+		my $translated = $output->translate_text($$self{'Text'});
+
+		# Write text, interpolated
+		$self->write_interpolated_text($output, $translated);
+	}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Variable.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Variable.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit/Variable.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,22 @@
+package WebAppFramework::Unit::Variable;
+use strict;
+use Symbol;
+use base 'WebAppFramework::Unit';
+
+# Unit to display a page variable (parameter, form entry, etc)
+
+# new() parameters:
+#	Variable => name of variable
+
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	if($phase == WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT)
+	{
+		# use base class to write out variable
+		$self->write_variable_text($output, $$self{'Variable'});
+	}
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Unit.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,1326 @@
+package WebAppFramework::Unit;
+use strict;
+use vars qw/%_defaults %WAF_fns %_phase_names/;
+
+# psudeo phase -- no output should be made!
+# Happens once after the page has been completely set up / before global files are output
+use constant PHASE_INITIALISE			=> 0;
+
+# Define constants first
+use constant PHASE_LANG_CPP__BEGIN 			=> 1;
+use constant PHASE_LANG_CPP_DECLARATION 	=> 1;
+use constant PHASE_LANG_CPP_HANDLE_VARS 	=> 2;
+use constant PHASE_LANG_CPP_HANDLE_SECURITY	=> 3;
+use constant PHASE_LANG_CPP_HANDLE_PREPARE	=> 4;
+use constant PHASE_LANG_CPP_HANDLE_OUTPUT	=> 5;
+use constant PHASE_LANG_CPP_FINISH			=> 6;
+use constant PHASE_LANG_CPP__END			=> 6;
+
+# Form module uses phases in range 80 -- 99
+
+use constant PHASE_MAIN_CPP__BEGIN		=> 100;
+use constant PHASE_MAIN_CPP_DECLARATION => 100;
+use constant PHASE_MAIN_CPP_CODE		=> 101;
+use constant PHASE_MAIN_CPP__END		=> 101;
+
+use constant PHASE_MAIN_H__BEGIN 		=> 200;
+use constant PHASE_MAIN_H_DECLARATION	=> 200;
+use constant PHASE_MAIN_H__END			=> 200;
+
+# global page
+use constant PHASE_GLOBAL_CPP__BEGIN	=> 300;
+use constant PHASE_GLOBAL_CPP_DECLARATION => 300;
+use constant PHASE_GLOBAL_CPP_CODE		=> 301;
+use constant PHASE_GLOBAL_CPP__END		=> 301;
+
+use constant PHASE_GLOBAL_H__BEGIN 		=> 400;
+use constant PHASE_GLOBAL_H_DECLARATION	=> 400;
+use constant PHASE_GLOBAL_H__END		=> 400;
+
+sub is_global_phase {my ($self,$p) = @_; return ($p >= PHASE_GLOBAL_CPP__BEGIN && $p <= PHASE_GLOBAL_CPP__END) || ($p >= PHASE_GLOBAL_H__BEGIN && $p <= PHASE_GLOBAL_H__END)}
+
+my %_phase_names = (
+		'lang_declaration' => &PHASE_LANG_CPP_DECLARATION,
+		'lang_vars' => &PHASE_LANG_CPP_HANDLE_VARS,
+		'lang_security' => &PHASE_LANG_CPP_HANDLE_SECURITY,
+		'lang_prepare' => &PHASE_LANG_CPP_HANDLE_PREPARE,
+		'lang_output' => &PHASE_LANG_CPP_HANDLE_OUTPUT,
+		'lang_finish' => &PHASE_LANG_CPP_FINISH,
+		'main_declaration' => &PHASE_MAIN_CPP_DECLARATION,
+		'main_code' => &PHASE_MAIN_CPP_CODE,
+		'main_h' => &PHASE_MAIN_H_DECLARATION,
+		'global_declaration' => &PHASE_GLOBAL_CPP_DECLARATION,
+		'global_code' => &PHASE_GLOBAL_CPP_CODE,
+		'global_h' => &PHASE_GLOBAL_H_DECLARATION
+	);
+
+# header in the per-language pages
+use constant HEADERS_SYSTEM 			=> 0;
+use constant HEADERS_PROJECT			=> 1;
+# headers for the page.h file
+use constant HEADERS_PAGE_H_SYSTEM 		=> 2;
+use constant HEADERS_PAGE_H_PROJECT		=> 3;
+# headers for global.h file
+use constant HEADERS_GLOBAL_H_SYSTEM 	=> 4;
+use constant HEADERS_GLOBAL_H_PROJECT	=> 5;
+
+# Then include other required modules
+use CppVariable;
+use WebAppFramework;
+use WebAppFramework::Output;
+use WebAppFramework::Unit::TranslatedText;
+use WebAppFramework::Unit::LinkToPage;
+use WebAppFramework::Unit::RawHTML;
+
+
+sub new
+{
+	my ($type, %params) = @_;
+	my $self = {};
+	bless $self, $type;
+
+	# set up initial structure
+	$$self{'_pre_units'} = [];
+	$$self{'_units'} = {};
+	$$self{'_post_units'} = [];
+	$$self{'_parameters'} = [];
+
+	# set default attributes
+	if(exists $_defaults{$type})
+	{
+		while(my ($k,$v) = each (%{$_defaults{$type}}))
+		{
+			$$self{$k} = $v
+		}
+	}
+
+	# add given attributes, which may override defaults
+	while(my ($k,$v) = each(%params))
+	{
+		if($k =~ m/\A_/)
+		{
+			die "Can't use _ to start parameter names for Units.";
+		}
+		elsif($k =~ m/\A\@(.+)\Z/)
+		{
+			# value is a unit (or text), store in appropraite place
+			my $pos = $1;
+			my $u = _preprocess_unit($v);
+			${$$self{'_units'}}{$pos} = $u;
+			$u->_set_parent($self);
+		}
+		else
+		{
+			$$self{$k} = $v;
+		}
+	}
+	
+	# Give the derived class a chance to do other work
+	$self->new_postcreate();
+
+	# return new object
+	$self
+}
+
+# Function called after everything is setup by new()
+sub new_postcreate
+{
+}
+
+# set defaults for future new operations
+sub set_defaults
+{
+	my ($type, %params) = @_;
+
+	# create a defaults entry for this type if necessary
+	$_defaults{$type} = {} unless exists $_defaults{$type};
+	
+	# entry to add attributes to
+	my $d = $_defaults{$type};
+
+	# Set all entries
+	while(my ($k,$v) = each(%params))
+	{
+		if($k =~ m/\A_/)
+		{
+			die "Can't use _ to start parameter names for Units (while setting defaults for $type).";
+		}
+		$$d{$k} = $v;
+	}
+}
+
+# adjust parameters after the new() event
+sub set
+{
+	my ($self, $k, $v) = @_;
+	die "Can't use _ to start parameter names for Units." if $k =~ m/\A_/;
+	$$self{$k} = $v;
+	return $self
+}
+
+# return parameters
+sub get
+{
+	my ($self, $k) = @_;
+	die "Parameter $k unknown" unless exists $$self{$k};
+	$$self{$k}
+}
+
+sub param_exists
+{
+	my ($self, $k) = @_;
+	return exists $$self{$k}
+}
+
+# used to pre-process a unit before adding it
+sub _preprocess_unit
+{
+	my ($unit) = @_;
+	if($unit =~ m/\A{([^}]+)}\Z/)
+	{
+		# single page variable, turn into a variable unit
+		return WebAppFramework::Unit::Variable->new('Variable' => $1)
+	}
+	if(!ref($unit))
+	{
+		# text unit
+		return WebAppFramework::Unit::TranslatedText->new('Text' => $unit);
+	}
+	
+	# Normal unit
+	$unit
+}
+
+# Add a Unit which will be output just before this one
+sub add_pre_unit
+{
+	my ($self, $unit) = @_;
+	$unit = _preprocess_unit($unit);
+	
+	unshift @{$$self{'_pre_units'}},$unit;
+	$unit->_set_parent($self);
+	$unit
+}
+
+# Add a unit at a given position
+sub add_unit
+{
+	my ($self, $position, $unit) = @_;
+	$unit = _preprocess_unit($unit);
+	
+	if(exists ${$$self{'_units'}}{$position})
+	{
+		die "Position $position is already in use"
+	}
+	
+	${$$self{'_units'}}{$position} = $unit;
+	$unit->_set_parent($self);
+	$unit
+}
+
+# Get a unit at a specified position
+sub get_unit
+{
+	my ($self, $position) = @_;
+	${$$self{'_units'}}{$position}
+}
+
+# Add some translated text at a position, returning the created unit
+sub add_text
+{
+	my ($self, $position, $text) = @_;
+
+	my $unit = WebAppFramework::Unit::TranslatedText->new('Text' => $text);
+	$self->add_unit($position, $unit);
+	return $unit
+}
+
+# Add a Unit which will be output just after this one
+sub add_post_unit
+{
+	my ($self, $unit) = @_;
+	$unit = _preprocess_unit($unit);
+	
+	push @{$$self{'_post_units'}}, $unit;
+	$unit->_set_parent($self);
+	$unit
+}
+
+# replace a unit with another unit (cannot be text, must be a real unit)
+sub replace_unit
+{
+	my ($self,$unit,$replace_with) = @_;
+
+	# go through pre units
+	my $pre = $$self{'_pre_units'};
+	for(my $i = 0; $i <= $#$pre; $i++)
+	{
+		if($$pre[$i] == $unit)
+		{
+			$$pre[$i] = $replace_with;
+			$replace_with->_set_parent($self);
+			return;
+		}
+	}
+
+	# this unit
+	# paranoid reset each position -- necessary for some usage semantics
+	keys %{$$self{'_units'}};
+	while(my ($k,$v) = each(%{$$self{'_units'}}))
+	{
+		if($v == $unit)
+		{
+			${$$self{'_units'}}{$k} = $replace_with;
+			$replace_with->_set_parent($self);
+			# reset each() position
+			keys %{$$self{'_units'}};
+			return;
+		}
+	}
+
+	# go through post-write units
+	my $post = $$self{'_post_units'};
+	for(my $i = 0; $i <= $#$post; $i++)
+	{
+		if($$post[$i] == $unit)
+		{
+			$$post[$i] = $replace_with;
+			$replace_with->_set_parent($self);
+			return;
+		}
+	}
+
+	die "Unit $unit '".$$unit{'Name'}."' passed to replace_unit could not be found"
+}
+
+
+# Add pre-and post units to surround this with a link
+sub link_to
+{
+	my ($self, @a) = @_;
+	
+	my $l = WebAppFramework::Unit::LinkToPage->new('Link' => [@a]);
+	$self->add_pre_unit($l);
+	$self->add_post_unit(WebAppFramework::Unit::RawHTML->new('HTML' => '</a>'));
+	return $l
+}
+
+# Accepts a lists of parameters, which are either CppVariables
+# or strings which can be turned into one.
+# In practise, this is just a utility array, and the Unit does nothing
+# with it. Used in for the top level page to contain a list of the
+# parameters for the page (parameters are added to the root anyway)
+sub add_parameters
+{
+	my ($self, @to_add) = @_;
+
+	if (exists $$self{'_parent'}) {return $$self{'_parent'}->add_parameters(@to_add)}
+
+	push @{$$self{'_parameters'}},CppVariable::var_list(@to_add);
+	$self->register_variable_namespace('params',$$self{'_parameters'});
+}
+
+# returns the list of parameteres
+sub get_parameters
+{
+	my ($self) = @_;
+	if (exists $$self{'_parent'}) {return $$self{'_parent'}->get_parameters()}
+	return @{$$self{'_parameters'}}
+}
+
+# returns a list of required headers, for that specific type
+sub get_required_headers
+{
+	my ($self, $type) = @_;
+
+	my @headers;
+	
+	# get headers from the other units
+	for(@{$$self{'_pre_units'}})
+	{
+		push @headers,$_->get_required_headers($type);
+	}
+	for(@{$$self{'_post_units'}})
+	{
+		push @headers,$_->get_required_headers($type);
+	}
+	# and then the units in the
+	for(values %{$$self{'_units'}})
+	{
+		push @headers,$_->get_required_headers($type);
+	}
+
+	# and finally, the ones for this unit itself
+	push @headers,$self->get_required_headers_this($type);
+	
+	# deduplicate
+	my %h;
+	$h{$_} = 1 for(@headers);
+	
+	# return the de-duplicated list
+	return keys %h;
+}
+
+# overload in unit implementation, to return any actual headers required
+sub get_required_headers_this
+{
+	my ($self, $type) = @_;
+
+	# return the empty list
+	return ();
+}
+
+# write the output for the unit in the given phase.
+# It shouldn't be necessary to override this.
+sub write
+{
+	my ($self, $output, $phase, $subphase) = @_;
+
+	# go through pre-write units
+	for(@{$$self{'_pre_units'}})
+	{
+		$_->write($output, $phase, $subphase);
+	}
+
+	# write this unit
+	$self->write_unit($output, $phase, $subphase);
+
+	# go through post-write units
+	for(@{$$self{'_post_units'}})
+	{
+		$_->write($output, $phase, $subphase);
+	}
+}
+
+# Override this for the Unit implementation
+# not a terribly useful implementation, as it just writes the contained
+# units in, effectively, a random order
+sub write_unit
+{
+	my ($self, $output, $phase, $subphase) = @_;
+	
+	for(values %{$$self{'_units'}})
+	{
+		$_->write($output, $phase, $subphase);
+	}
+}
+
+# internal
+sub _set_parent
+{
+	my ($self, $parent) = @_;
+	
+	die "Internal logic error: parent being set to own unit" if $self == $parent;
+	
+	$$self{'_parent'} = $parent;
+}
+
+# Get the parent Unit
+sub get_parent
+{
+	my ($self) = @_;
+
+	return $$self{'_parent'};
+}
+
+# Get the root Unit
+sub get_root
+{
+	my ($self) = @_;
+
+	return $$self{'_parent'}->get_root() if exists $$self{'_parent'};
+
+	return $self;
+}
+
+# find the parent in the heirarchy of a given type
+sub get_parent_of_type
+{
+	my ($self, $type) = @_;
+	
+	if(!exists $$self{'_parent'})
+	{
+		die "Parent of type $type not found"
+	}
+	
+	if($$self{'_parent'}->isa($type))
+	{
+		return $$self{'_parent'}
+	}
+	
+	return $$self{'_parent'}->get_parent_of_type($type)
+}
+
+# Register a set of variables to a namespace
+# Can be a reference to an array or a function to generate the relevant CppVariable
+sub register_variable_namespace
+{
+	my ($self, $name, $vars_ref) = @_;
+
+	if (exists $$self{'_parent'}) {return $$self{'_parent'}->register_variable_namespace($name, $vars_ref)}
+	
+	$$self{'_vars'} = {} unless exists $$self{'_vars'};
+	
+	${$$self{'_vars'}}{$name} = $vars_ref;
+}
+
+sub get_variable
+{
+	my ($self, $varname) = @_;
+
+	# If $varname is actually a CppVariable, return it now
+	return $varname if ref($varname) eq 'CppVariable';
+
+	# It's a textual reference to a CppVariable, return it processed into a CppVariable now
+	return cppvar($1) if $varname =~ m/\A=(.+)\Z/;
+
+	# It's a link specification, return an expression which evalutes to a std::string
+	if($varname =~ m/\A\[(.+)\]\Z/)
+	{
+		# evalute link specification
+		my @link_spec = eval('('.$1.')');
+		if($@ ne '')
+		{
+			print "Could not parse link specification as embedded page variable:\n  Error: $@\n  Parameters: $1\n";
+			exit(1);
+		}
+		# generate and return a variable containing the link spec
+		return cppvar('std::string', $self->generate_page_address_expression(@link_spec))
+	}
+
+	# redirect to root
+	if(exists $$self{'_parent'}) {return $$self{'_parent'}->get_variable($varname)}
+
+	# split up such that namespaces can have .'s in them
+	die "Variable name '$varname' doesn't specify namespace"
+		unless $varname =~ m/\A(.+)\.([^\.]+)\Z/;
+	my ($namespace,$varwithin) = ($1,$2);
+	
+	# is it the raw form data space?
+	if($namespace eq 'formdata')
+	{
+		# an easy response
+		return cppvar('std::string', qq`GetFormDataString(rRequest, std::string("$varwithin"))`)
+	}
+	
+	# is it in the cookies namespace?
+	if($namespace eq 'cookie')
+	{
+		return cppvar('std::string', qq`rRequest.GetCookie("$varwithin")`)
+	}
+	
+	# is it in the config variables namespace?
+	if($namespace eq 'config')
+	{
+		return cppvar('std::string', qq`GetConfigurationVariable("$varwithin")`)
+	}
+	
+	# otherwise, find it in the registered namespaces
+	die "Variable namespace $namespace doesn't exist"
+		unless(exists ${$$self{'_vars'}}{$namespace});
+	my $r = ${$$self{'_vars'}}{$namespace};
+
+	if(ref($r) eq 'ARRAY')
+	{
+		# standard array reference
+		for my $v (@$r)
+		{
+			if($v->name() eq $varwithin)
+			{
+				# build a variable to return
+				return cppvar($v->type(), $namespace.'.Get'.$varwithin.'()');
+			}
+		}
+	}
+	elsif(ref($r) eq 'CODE')
+	{
+		# a function to find the variable
+		my $var = &$r($namespace, $varwithin);
+		return $var if defined $var;
+	}
+	else
+	{
+		die "Variable namespace $namespace has been registered with a ref of type ".ref($r).", which cannot be used."
+	}
+	
+	die "Could not find variable $varname";
+}
+
+
+# given an Output object and Variable name, write the variable into the HTML
+# If the string is of the form CONSTANT:value, then the value is written as a
+# simple constant.
+sub write_variable_text
+{
+	my ($self, $output, $varname) = @_;
+
+	if($varname =~ m/\ACONSTANT:"?(.*)"?\Z/)
+	{
+		# just write the constant
+		$output->write_text($1);
+		return;
+	}
+
+	# It's a link specification, write it efficiently
+	if($varname =~ m/\A\[(.+)\]\Z/)
+	{
+		# evalute link specification
+		my @link_spec = eval('('.$1.')');
+		if($@ ne '')
+		{
+			print "Could not parse link specification as embedded page variable in translated text:\n  Error: $@\n  Parameters: $1\n";
+			exit(1);
+		}
+		# write out this variable as efficiently as possible
+		$self->write_page_address($output, @link_spec);
+		return;
+	}
+
+	# get the variable
+	my $v = $self->get_variable($varname);
+	
+	# is a string? These need to be handled specially, to make sure
+	# users don't do nasty things.
+	if($v->type() eq 'std::string')
+	{
+		$output->write_code("\trResponse.WriteStringDefang(".$v->name().");\n")
+	}
+	else
+	{
+		# convert it to a string
+		my $converted = $v->convert_to('std::string');
+		$output->write_code("\trResponse.WriteString($converted);\n")
+	}
+}
+
+# interpolation
+sub write_interpolated_text
+{
+	my ($self,$output,$text) = @_;
+
+	# Write it, perhaps interpolating with variables?
+	if($text =~ m/{.+?}/)
+	{
+		# yes, output more complicated structure
+		my @t = split /{(.+?)}/,$text;
+		while($#t >= 0)
+		{
+			my ($tx,$var); ($tx,$var,@t) = @t;
+			# write text
+			$output->write_text($tx);
+			# write variable?
+			if($var ne '')
+			{
+				# use the base class to do it
+				$self->write_variable_text($output, $var);
+			}
+		}
+	}
+	else
+	{
+		# no interpolation, just output simple text
+		$output->write_text($text);
+	}
+}
+
+
+sub dump_structure
+{
+	my ($self, $f, $pos, $level) = @_;
+
+	# pre units
+	my $posp = $pos; $posp =~ s/\A@//;
+	for(@{$$self{'_pre_units'}})
+	{
+		$_->dump_structure($f, "pre-$posp", $level);
+	}
+
+	ref($self) =~ m/([^:]+)\Z/;
+	
+	print $f "\t" x $level, "$1 ($pos)\t\t";
+	for my $k (sort(keys(%$self)))
+	{
+		my $v = $$self{$k};
+		next if $k =~ m/\A_/;
+		while(ref($v))
+		{
+			# try to find an extra something from it if it's not a straight scalar
+			if(ref($v) eq 'ARRAY')
+			{
+				$v = $$v[0]
+			}
+			elsif(ref($v) eq 'HASH')
+			{
+				my (undef,$vv) = each %$v;
+				# reset each position
+				keys %$v;
+				# store
+				$v = $vv;
+			}
+			elsif(ref($v) eq 'SCALAR')
+			{
+				$v = $$v
+			}
+			else
+			{
+				# give up
+				last
+			}
+		}
+		# did it manage to find something printable?
+		if(ref($v))
+		{
+			next;
+		}
+		# display it as a possible truncated string
+		my $vp = $v;
+		$vp =~ s/\s+/ /g;	# collapse whitespace
+		if(length($vp) > 30)
+		{
+			print $f "$k=",substr($vp,0,30),"... ";
+		}
+		else
+		{
+			print $f "$k=$vp "
+		}
+	}
+	print $f "\n";
+
+	# Units within this one
+	for my $p (sort(keys(%{$$self{'_units'}})))
+	{
+		${$$self{'_units'}}{$p}->dump_structure($f, '@'.$p, $level + 1);
+	}
+	
+	# post units
+	for(@{$$self{'_post_units'}})
+	{
+		$_->dump_structure($f, "post-$posp", $level);
+	}
+}
+
+# call on root only
+sub set_webapp
+{
+	my ($self, $webapp) = @_;
+	$$self{'_webapp'} = $webapp;
+}
+
+sub get_webapp
+{
+	my ($self) = @_;
+	if (exists $$self{'_parent'}) {return $$self{'_parent'}->get_webapp()}
+	$$self{'_webapp'}
+}
+
+# call on root only
+sub set_locale
+{
+	my ($self, $locale) = @_;
+	$$self{'_locale'} = $locale;
+}
+
+sub get_locale
+{
+	my ($self) = @_;
+	if (exists $$self{'_parent'}) {return $$self{'_parent'}->get_locale()}
+	$$self{'_locale'}
+}
+
+# call on root only
+sub set_pagename
+{
+	my ($self, $pagename) = @_;
+	$$self{'_pagename'} = $pagename;
+}
+
+sub get_pagename
+{
+	my ($self) = @_;
+	if (exists $$self{'_parent'}) {return $$self{'_parent'}->get_pagename()}
+	$$self{'_pagename'}
+}
+
+# return all the units below this unit, essentially in a random order
+sub flatten_heirarchy
+{
+	my ($self) = @_;
+
+	my @flattened = @{$$self{'_pre_units'}};
+	for(values %{$$self{'_units'}})
+	{
+		push @flattened, $_, $_->flatten_heirarchy()
+	}
+	push @flattened, @{$$self{'_post_units'}};
+
+	@flattened
+}
+
+# return link to this page, with different parameters
+sub write_page_address_this
+{
+	my ($self, $output, @a) = @_;
+	$self->write_page_address($self, $output, $self->get_pagename(), @a);
+}
+
+sub write_page_address
+{
+	my ($self, $output, @a) = @_;
+	
+	# generate link data
+	my @link = $self->_generate_page_address(1, @a); # 1 -- relative allowed
+
+	# write out data, and strings
+	my $is_constant = 1;
+	for(@link)
+	{
+		if($is_constant)
+		{
+			$output->write_text($_);
+		}
+		else
+		{
+			$output->write_code("\trResponse.WriteString($_);\n");
+		}
+		$is_constant = !$is_constant;
+	}
+}
+
+# return a expression which evaluates to a std::string of the link URL
+sub generate_page_address_expression
+{
+	my ($self, @a) = @_;
+
+	# generate link data
+	my ($first_constant, @link) = $self->_generate_page_address(0, @a); # 0 -- relative not allowed!
+
+	# create the initial starting point
+	my $expression = 'std::string('.WebAppFramework::Output::string_to_cpp_static_string($first_constant, 2).')';
+
+	# write out data, and strings
+	my $is_constant = 0;
+	for(@link)
+	{
+		if($is_constant)
+		{
+			$expression .= '+'.WebAppFramework::Output::string_to_cpp_static_string($_,2)
+		}
+		else
+		{
+			$expression .= '+'.$_
+		}
+		$is_constant = !$is_constant;
+	}
+	
+	$expression
+}
+
+# return a link specification into something which can be embedded in code
+# (can be used to avoid auto-generated code having to write nasty code
+sub link_spec_to_WAF_code
+{
+	my ($self, $link_to, @link_params) = @_;
+	
+	# allow parameters to be passed as an anonymous array.
+	($link_to, @link_params) = @{$link_to} if ref($link_to);
+	
+	my $params = '';
+	while($#link_params >= 0)
+	{
+		my ($a,$b);
+		($a,$b,@link_params) = @link_params;
+		$params .= ",'$a'=>'$b'";
+	}
+	
+	"WAF::Link('$link_to'$params)"
+}
+
+# return code suitable for a handler function, which redirects the user to another page
+sub make_redirect_code
+{
+	my ($self, @a) = @_;
+	
+	my $link_expression = $self->generate_page_address_expression(@a);
+	
+	<<__E;
+		{
+			std::string uri($link_expression);
+			rResponse.SetAsRedirect(uri.c_str());
+		}
+		// Finished generating response
+		return true;
+__E
+}
+
+sub _generate_page_address
+{
+	my ($self, $relative_allowed, $pagename, %param_values) = @_;
+	
+	# get the two parameter lists, the global parameters (which will be at the start
+	# of both the parameter lists)
+	my @this_param_list = $self->get_parameters();
+	my @to_param_list = $self->get_webapp()->get_page_parameters($pagename);
+	my @global_params = $self->get_webapp()->get_global_parameters();
+	
+	# try to fill in as many of the parameters as possible from the list
+	# of parameter values supplied
+	my @values;
+	while(my ($p,$v) = each %param_values)
+	{
+		# search for the name in the to param list
+		my $i;
+		for($i = 0; $i <= $#to_param_list; $i++)
+		{
+			if($to_param_list[$i]->name() eq $p)
+			{
+				# store the value in the right place
+				$values[$i] = $v;
+				last;
+			}
+		}
+		if($i > $#to_param_list)
+		{
+			die "Page $pagename has no parameter $p"
+		}
+	}
+	
+	# build the start of the address
+	my $addr_start = $self->get_webapp()->get_url_base_for_page($pagename);
+	# and where the adding of parameters should start (relative will omit some by increasing this number)
+	my $first_param = 0;
+	
+	# if the too page has no parameters, stop now!
+	if($#to_param_list < 0)
+	{
+		return ($addr_start)
+	}
+	
+	# if the page names aren't the same, then try to fill missing
+	# spaces in the page parameters from the defaults
+	if($pagename ne $self->get_pagename())
+	{
+		for(my $d = $#global_params + 1; $d <= $#to_param_list; $d++)
+		{
+			if($values[$d] eq '')
+			{
+				# blank, copy in a default if it's available
+				if($to_param_list[$d]->has_default())
+				{
+					$values[$d] = 'CONSTANT:'.$to_param_list[$d]->default_value();
+				}
+			}
+		}
+	}
+	else
+	{
+		# link goes to the current page...
+		if($relative_allowed)
+		{
+			# consider relative addressing -- how many values aren't specified at the beginning of the list?
+			my $unspecified = 0;
+			# NOTE: Correctly use < rather than <= in the statment below
+			# so that the last value is never identified as a unspecified value.
+			# This is so there's at least one .. in the results
+			#                 \!/
+			while($unspecified < $#to_param_list && $values[$unspecified] eq '')
+			{
+				$unspecified++
+			}
+			my $dotdots = join('/',('..') x ((($#to_param_list + 1) - $unspecified) - 1));
+			if(length($dotdots) < length($addr_start))
+			{
+				# it's an output size optimisation! Use relative addressing
+				$addr_start = $dotdots;
+				$first_param = $unspecified;
+			}
+		}
+	}
+	
+	# Now generate a list of items representing the text.
+	my @link = ($addr_start);
+	for(my $p = $first_param; $p <= $#to_param_list; $p++)
+	{
+		$link[$#link] .= '/';
+	
+		# is value not specified?
+		if($values[$p] eq '')
+		{
+			# can this be obtained from the current values?
+			if(($pagename eq $self->get_pagename()) || $p <= $#global_params)
+			{
+				# yes, because it's either on the same page, or one of the global ones.
+				push @link, "rElements[".(WebAppFramework::URL_ELEMENTS_PARAMS_START + $p)."]",''
+			}
+			else
+			{
+				die "When generating link for page $pagename, parameter ".$to_param_list[$p]->name()." could not be determined"
+			}
+		}
+		else
+		{
+			# convert the value to a string, and put it on the list
+			if($values[$p] =~ m/\ACONSTANT:"?(.+)"?\Z/)
+			{
+				# constant value -- just add to previous text
+				$link[$#link] .= $1
+			}
+			# local variable?
+			elsif($values[$p] =~ m/\ALOCAL:"?(.+)"?\Z/)
+			{
+				# make a CppVariable out of it, push to link with blank text string
+				push @link,cppvar($1)->convert_to('std::string'),'';
+			}
+			else
+			{
+				# non-constant
+				my $v = $values[$p];
+				unless(ref($v))
+				{
+					# Not a CppVariable, so assume it's a variable name
+					$v = $self->get_variable($v)
+				}
+				# convert it to a std::string, add add a blank bit of text at the end
+				push @link,$v->convert_to('std::string'),'';
+			}
+		}
+	}
+	# trim any blank bit of text from the end of this link
+	pop @link if $link[$#link] eq '';
+	# fix up relative links which are relative for the last parameter only
+	if($addr_start eq '' && $link[0] =~ m!\A/!)
+	{
+		$link[0] =~ s!\A/!!;
+	}
+	
+	# return the link data
+	return @link
+}
+
+# support for a list of translated strings, which can be used if extra code
+# requires 
+sub add_translated_string
+{
+	my ($self, $name, $string) = @_;
+	
+	# only add this to the root
+	if(exists $$self{'_parent'})
+	{
+		$$self{'_parent'}->add_translated_string($self, $name, $string);
+		return;
+	}
+	
+	# add to the list (creating the list if it doesn't already exist)
+	$$self{'_translated_strings'} = [] unless exists $$self{'_translated_strings'};
+	push @{$$self{'_translated_strings'}}, [$name, $string]
+}
+
+sub write_translated_strings
+{
+	my ($self, $output, $write_defines) = @_;
+	
+	# check stuff
+	if(exists $$self{'_parent'})
+	{
+		$$self{'_parent'}->write_translated_strings($self, $write_defines);
+		return;
+	}
+	return unless exists $$self{'_translated_strings'};
+	
+	my $a = $$self{'_translated_strings'};
+	if($write_defines)
+	{
+		my $pn = uc($self->get_pagename());
+		$output->write_code("\n");
+		for(my $i = 0; $i <= $#$a; $i++)
+		{
+			$output->write_code("#define ${pn}_TRANSLATED_STRING_".${$$a[$i]}[0]."\t\t$i\n")
+		}
+		$output->write_code("\n");
+	}
+	else
+	{
+		$output->write_code("static const char *PageTranslatedStrings[] = {\n");
+		for(my $i = 0; $i <= $#$a; $i++)
+		{
+			my $str = WebAppFramework::Output::string_to_cpp_static_string($output->translate_text(${$$a[$i]}[1]));
+			$output->write_code($str.(($i == $#$a)?"\n":",\n"));
+		}
+		$output->write_code("};\n\n")
+	}
+}
+
+
+# This belongs to the Form implementation. Used for finding fragement text
+# within an arbitary heirarchy. Not nice to have it here, but OK.
+sub get_form_template_fragment
+{
+	return undef;
+}
+
+# Another thing for the forms implementation
+sub is_form_item
+{
+	return 0;
+}
+
+
+# Code pre-processing, implement the WAF::* functions!
+sub preprocess_code
+{
+	my ($self, $code, $output) = @_;
+	
+	# find text to translate, and translate it
+	$code =~ s/<<<<(.+?)>>>>/$self->preprocess_code_transtext($output,$1)/ges;
+
+	# find all functions, and modify them
+	$code =~ s/(\(([^\(\)]+?)\))?\s*WAF::(\w+?)\((.+?)\)/$self->preprocess_code_WAF($2,$3,$4)/ges;
+
+	# return modified code
+	$code
+}
+
+sub preprocess_code_transtext
+{
+	my ($self, $output, $text) = @_;
+	
+	# translate it first
+	my $translated = $output->translate_text($text);
+	
+	# then split it and output the various bits
+	my $o = '(';
+	
+	# split up to get translated bits
+	my $done_first = 0;
+	my @t = split /{(.+?)}/,$translated;
+	while($#t >= 0)
+	{
+		my ($tx,$var); ($tx,$var,@t) = @t;
+		if(!$done_first)
+		{
+			$done_first = 1;
+			$o .= 'std::string('.WebAppFramework::Output::string_to_cpp_static_string($tx,0).')'
+		}
+		elsif($tx ne '')
+		{
+			$o .= "\n + ".WebAppFramework::Output::string_to_cpp_static_string($tx,0)
+		}
+		# get the variable
+		if($var ne '')
+		{
+			$o .= "\n + ".$self->get_variable($var)->convert_to('std::string')
+		}
+	}
+	
+	$o .= ')';
+	
+	$o
+}
+
+my %WAF_fns = (
+	'SetCookie' => \&preprocess_code_WAF_SetCookie,
+	'Var' => \&preprocess_code_WAF_Var,
+	'Link' => \&preprocess_code_WAF_Link
+);
+
+sub preprocess_code_WAF_SetCookie
+{
+	my ($self, $output_type, $cookie_name, $source_variable) = @_;
+	
+	return 'rResponse.SetCookie("'.$cookie_name.'", '.($self->get_variable($source_variable)->convert_to('std::string')).'.c_str(), "'
+		.$self->get_webapp()->get_url_base().'")';
+}
+
+sub preprocess_code_WAF_Var
+{
+	my ($self, $output_type, $source_variable) = @_;
+
+	# Get the variable
+	my $var = $self->get_variable($source_variable);
+	# By default, don't convert the variable
+	if($output_type eq '')
+	{
+		# output it raw
+		return $var->name();
+	}
+	else
+	{
+		# convert it
+		return $var->convert_to($output_type);
+	}
+
+	return 'LOGICAL ERROR'
+}
+
+sub preprocess_code_WAF_Link
+{
+	my ($self, $output_type, @link_spec) = @_;
+
+	# Check type is default or std::string if explicit
+	unless($output_type eq '' || $output_type eq 'std::string')
+	{
+		die "WAF::Link cannot be cast to anything other than std::string\n";
+	}
+
+	# output text
+	$self->generate_page_address_expression(@link_spec)
+}
+
+# change a psuedo-function into real output code
+sub preprocess_code_WAF
+{
+	my ($self, $output_type, $function_name, $parameters) = @_;
+
+	# Decode parameters
+	my @params = eval('('.$parameters.')');
+	if($@ ne '')
+	{
+		print "Could not parse parameters for WAF::$function_name:\n  Error: $@\n  Parameters: $parameters\n";
+		exit(1);
+	}
+	
+	# Check function exists
+	unless(exists $WAF_fns{$function_name})
+	{
+		print "Psueodo function WAF::$function_name is not known.\n";
+		exit(1);
+	}
+	
+	# Return the processed function
+	&{$WAF_fns{$function_name}}($self,$output_type,@params);
+}
+
+sub phase_name_to_number
+{
+	my ($self,$phase_name) = @_;
+	die "Unknown phase name $phase_name" unless exists $_phase_names{$phase_name};
+	$_phase_names{$phase_name}
+}
+
+sub call_in_default_page()
+{
+	my ($self) = @_;
+
+	# call on pre-write units
+	for(@{$$self{'_pre_units'}})
+	{
+		$_->in_default_page();
+	}
+
+	# call on this unit
+	$self->in_default_page();
+
+	# call on post-write units
+	for(@{$$self{'_post_units'}})
+	{
+		$_->in_default_page();
+	}
+
+}
+
+# default implementation for above
+sub in_default_page()
+{
+}
+
+# utility function -- takes either an array of items
+# or a space separated list, and returns an array
+sub list_to_array
+{
+	my ($self,$r) = @_;
+	if($r eq '')
+	{
+		return ()
+	}
+	elsif(ref($r) eq 'ARRAY')
+	{
+		return @$r;
+	}
+	elsif(!ref($r))
+	{
+		return split /\s+/,$r
+	}
+	else
+	{
+		die "Anonymous ref to array or scalar space separated list expected"
+	}
+}
+
+# find the first unit of a given type (or partial type) which has specified parameters.
+# for example, $page->find_unit('Form', 'Name' => 'login');
+# returns undef if it cannot be found.
+sub find_unit
+{
+	my ($self, $type, %params) = @_;
+
+	UNIT: for my $u (@{$$self{'_pre_units'}}, values %{$$self{'_units'}}, @{$$self{'_post_units'}})
+	{
+		# try and find units within this
+		my $ub = $u->find_unit($type, %params);
+		return $ub if defined $ub;
+		
+		if(substr(ref($u), 0-length($type)) eq $type)
+		{
+			# type matches, make sure all arguments match
+			while(my ($k,$v) = each(%params))
+			{
+				if($$u{$k} ne $v)
+				{	
+					# reset each position
+					keys %params;
+					next UNIT;
+				}
+			}
+			
+			# all good, return it
+			return $u
+		}
+	}
+
+	# nothing found
+	return undef;
+}
+
+# iterate through all subunits. Calls the function supplied for
+# each unit (NOT including this one) with args
+#	ref to unit
+#	ref to parent
+#	position (undef if pre or post unit)
+#	'pre','post',undef	depending on where it is.
+# If the function returns a true value, the search is aborted.
+# (use closures (anonymous sub {}) to pass other data into this function)
+sub interate_through_subunits
+{
+	my ($self,$function) = @_;
+
+	# pre units	
+	
+	# go through pre units
+	for(@{$$self{'_pre_units'}})
+	{
+		return 1 if &$function($_, $self, undef, 'pre');
+		return 1 if $_->interate_through_subunits($function);
+	}
+
+	# positioned sub-units
+	keys %{$$self{'_units'}}; # necessary sometimes
+	while(my ($k,$v) = each(%{$$self{'_units'}}))
+	{
+		if(&$function($v, $self, $k, undef)
+			|| $v->interate_through_subunits($function))
+		{
+			# reset each() position
+			keys %{$$self{'_units'}};
+			return 1;
+		}
+	}
+
+	# go through post-write units
+	for(@{$$self{'_post_units'}})
+	{
+		return 1 if &$function($_, $self, undef, 'post');
+		return 1 if $_->interate_through_subunits($function);
+	}
+	return 0;
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/email.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/email.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/email.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,58 @@
+package WebAppFramework::Validator::email;
+use strict;
+use base 'WebAppFramework::Validator';
+use WebAppFramework::Unit;
+
+sub get_required_headers_this
+{
+	my ($self, $system_headers) = @_;
+
+	if(!$system_headers)
+	{
+		return ('ValidateEmailAddress.h')
+	}
+	
+	()
+}
+
+sub generate_code
+{
+	my ($self, $var, $boolean_result) = @_;
+	
+	# check the var is of the right type
+	if($var->type() ne 'std::string')
+	{
+		die "WebAppFramework::Validator::email passed a non-std::string value to validate"
+	}
+
+	# read the modifier
+	my $do_lookup = 'true';
+	if($$self{'_modifier'} eq 'no-lookup')
+	{
+		$do_lookup = 'false';
+	}
+	elsif($$self{'_modifier'} ne '')
+	{
+		die "WebAppFramework::Validator::email only accepts 'no-lookup' as a modifier"
+	}
+
+	# name of this variable
+	my $toValidate = $var->name();
+
+	# Return the code
+	return <<__E
+		{
+			std::string canonical;
+			$boolean_result = ValidateEmailAddress($toValidate, canonical, $do_lookup);
+			$toValidate = canonical;
+		}
+__E
+}
+
+sub generate_default_error_message
+{
+	my ($self, $noun) = @_;
+	$noun . ' must be a valid email address'
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/phone.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/phone.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator/phone.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,34 @@
+package WebAppFramework::Validator::phone;
+use strict;
+use base 'WebAppFramework::Validator';
+
+sub generate_code
+{
+	my ($self, $var, $boolean_result) = @_;
+	
+	# check the var is of the right type
+	if($var->type() ne 'std::string')
+	{
+		die "WebAppFramework::Validator::phone passed a non-std::string value to validate"
+	}
+
+	# name of this variable
+	my $toValidate = $var->name();
+
+	# Return the code
+	return <<__E
+		{
+			std::string canonical;
+			$boolean_result = WAFUtility::ValidatePhoneNumber($toValidate, canonical);
+			if($boolean_result) {$toValidate = canonical;}
+		}
+__E
+}
+
+sub generate_default_error_message
+{
+	my ($self, $noun) = @_;
+	$noun . ' must be a valid phone number'
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework/Validator.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,92 @@
+package WebAppFramework::Validator;
+use strict;
+
+# new() is always called from TextField with a single string as a modifer
+# Other validators outside this system can use other schemes.
+sub new
+{
+	my ($type, $modifer) = @_;
+	my $self = {};
+	bless $self, $type;
+
+	# store modifer argument
+	$$self{'_modifier'} = $modifer;
+
+	# return object
+	return $self
+}
+
+
+# Return a list of the headers this unit requires. $system_headers
+# is a true value if system headers are being queried.
+sub get_required_headers_this
+{
+	my ($self, $system_headers) = @_;
+
+	# by default, return nothing
+	()
+}
+
+
+# Generate the code to validate the input
+# Args are:
+#	CppVariable of the source of the data
+#	Name of the variable of type bool which should be set to true if the data validates
+# NOTE: Validators are allowed to modify the variable to set it to a more "canonical form",
+# so it must be an actual variable, not a function call.
+sub generate_code
+{
+	my ($self, $var, $boolean_result) = @_;
+	
+	die "WebAppFramework::Validator base class called to generate_code"
+}
+
+
+# Generate a default error message in the default language for when validation fails.
+# The noun is the name of the item which has failed validation.
+sub generate_default_error_message
+{
+	my ($self, $noun) = @_;
+	
+	die "WebAppFramework::Validator base class called to generate_default_error_message"
+}
+
+
+# Utility function to load a validator given a string of name(modifier)
+sub load_builtin
+{
+	my ($specify) = @_;
+
+	# split the specification into name and modifier
+	my ($name,$modifier) = (undef,undef);
+	if($specify =~ /\A\w+\Z/)
+	{
+		$name = $specify
+	}
+	elsif($specify =~ /\A(\w+)\((.*)\)\Z/)
+	{
+		$name = $1;
+		$modifier = $2;
+	}
+	else
+	{
+		die "Invalid Validator specifier '$specify'\n"
+	}
+
+	# see if an appropraite builtin module can be loaded
+	my $objtype = 'WebAppFramework::Validator::'.$name;
+	my $validator;
+	eval <<__E;
+		use $objtype;
+		\$validator = $objtype->new(\$modifier);
+__E
+	if($@)
+	{
+		die "Failed to automatically load builtin validator of type '$name' (specifier was '$specify')\n";
+	}
+	
+	# return the loaded module
+	$validator
+}
+
+1;

Added: box/features/codeforintegration/lib/webappframework/WebAppFramework.pm
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFramework.pm	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFramework.pm	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,942 @@
+package WebAppFramework;
+use strict;
+use Symbol;
+use vars qw/%_file_ext_to_mime_type/;
+
+use constant LANG_NAME => 0;
+
+use constant PAGE_SHORT_NAME => 0;
+use constant PAGE_PARAMS_START => 1;
+
+use constant URL_ELEMENTS_PARAMS_START => 3;
+
+# include modules
+use WebAppFramework::Unit;
+use WebAppFramework::Output;
+use CppVariable;
+use CppDataClass;
+
+# define MIME types
+%_file_ext_to_mime_type = (
+	'html' => 'text/html; charset=UTF-8',
+	'txt' => 'text/plain',
+	'css' => 'text/css',
+	'jpg' => 'image/jpeg',
+	'jpeg' => 'image/jpeg',
+	'gif' => 'image/gif',
+	'png' => 'image/png',
+	'ico' => 'image/vnd.microsoft.icon'
+);
+
+sub new
+{
+	my $self = {};
+	$$self{'languages'} = [];
+	$$self{'languages_locale_exception'} = {};
+	$$self{'default_language'} = '';
+	$$self{'global_params'} = [];
+	$$self{'pages'} = {};
+	$$self{'webappname'} = 'generic-webapp';
+	$$self{'current_language'} = 'NOT-SET';
+	$$self{'static_dirs'} = [];
+	bless $self;
+	$self
+}
+
+sub set_webapp_name
+{
+	my ($self, $name, $daemon_name, $url_base) = @_;
+	
+	$$self{'webappname'} = $name;
+	$$self{'daemon_name'} = $daemon_name;
+	$$self{'url_base'} = $url_base;
+}
+
+sub get_webapp_name
+{
+	my ($self) = @_;
+	
+	return $$self{'webappname'}
+}
+
+sub add_language
+{
+	my ($self, $language_name, $locale_obj) = @_;
+	
+	die "Language name '$language_name' is too long (4 chars max)"
+		unless length($language_name) <= 4;
+	
+	push @{$$self{'languages'}},[$language_name];
+	
+	# if a locale is specified, store it
+	if(defined $locale_obj)
+	{
+		${$$self{'languages_locale_exception'}}{$language_name} = $locale_obj
+	}
+}
+
+sub set_default_langage
+{
+	my ($self, $default_language) = @_;
+	
+	$$self{'default_language'} = $default_language;
+}
+
+sub set_current_langage
+{
+	my ($self, $language) = @_;
+	
+	$$self{'current_language'} = $language;
+}
+
+sub add_global_parameters
+{
+	my ($self, @params) = @_;
+
+	push @{$$self{'global_params'}}, CppVariable::var_list(@params)
+}
+
+sub get_global_parameters
+{
+	my ($self, @params) = @_;
+
+	return @{$$self{'global_params'}};
+}
+
+sub get_page_parameters
+{
+	my ($self, $pagename) = @_;
+	
+	die "Page $pagename not known"
+		unless exists ${$$self{'pages'}}{$pagename};
+	
+	my $pi = ${$$self{'pages'}}{$pagename};
+	# return global parameters, followed by this page's parameteres
+	return (@{$$self{'global_params'}}, @{$pi}[PAGE_PARAMS_START .. $#$pi]);
+}
+
+sub add_page
+{
+	my ($self, $name, $short_name, @params) = @_;
+	
+	die "Short name '$short_name' for page '$name' is not exactly four characters long"
+		unless length($short_name) == 4;
+	
+	${$$self{'pages'}}{$name} = [$short_name, CppVariable::var_list(@params)];
+}
+
+sub add_extra_config_directive
+{
+	my ($self, $type, $name) = @_;
+
+	$$self{'extra_config_directives'} = [] unless exists $$self{'extra_config_directives'};
+	push @{$$self{'extra_config_directives'}},[$type,$name]
+}
+
+# arg 1 is redirect/rewrite, arg 2 is URL base
+sub set_homepage
+{
+	my ($self, $type, $uri) = @_;
+	print "homepage type must be 'redirect' or 'rewrite'" unless $type eq 'redirect' || $type eq 'rewrite';
+	$$self{'homepage'} = [$type, $uri];
+}
+
+sub initialise_page
+{
+	my ($self, $page, $pagename) = @_;
+	
+	# find the page
+	die "Can't find page $pagename in list of known pages" unless exists ${$$self{'pages'}}{$pagename};
+	my $pi = ${$$self{'pages'}}{$pagename};
+
+	# tell page about the web application
+	$page->set_webapp($self);
+	
+	# and it's name
+	$page->set_pagename($pagename);
+
+	# set global parameters
+	$page->add_parameters(@{$$self{'global_params'}});
+	# and the page local ones
+	$page->add_parameters(@{$pi}[PAGE_PARAMS_START .. $#{$pi}]);
+}
+
+sub write_makefile
+{
+	my ($self, $makefile_f, $app_description) = @_;
+	
+	my $webappname = $$self{'webappname'};
+	
+	# build a list of HTML files for the dependencies
+	opendir DIR, "Templates";
+	my $html_files = join(' ',map {'Templates/'.$_} grep(/\.html\Z/, readdir(DIR)));
+	closedir DIR;
+
+	# build a list of language files for the dependencies
+	my @langfiles = ('default');
+	for my $lang (@{$$self{'languages'}})
+	{
+		push @langfiles,$$lang[LANG_NAME]
+			unless $$lang[LANG_NAME] eq $$self{'default_language'}
+	}
+	my $language_files = join(' ',map {'Languages/'.$_.'.txt'} @langfiles);
+
+	while(my ($nm,$pi) = each %{$$self{'pages'}})
+	{
+		# test for existance
+		if(!-f "Pages/$nm.pl")
+		{
+			die "Page definition script Pages/$nm.pl does not exist";
+		}
+		
+		# dependencies for the page
+		my $deps = "Pages/$nm.pl $app_description ../../lib/webappframework/WebAppFramework.pm";
+		# command to run to generate these pages (base)
+		my $page_cmd = "perl ../../lib/webappframework/WebApplication.pl $app_description page $nm";
+		
+		# write makefile entries
+		# 1. The common code and header file
+		print $makefile_f "autogen_webapp/${webappname}Page$nm.cpp autogen_webapp/${webappname}Page$nm.h: $deps\n\t$page_cmd\n\n";
+
+		# run the script to generate the first files
+		die "Could not generate page $nm" unless system($page_cmd) == 0;
+
+		# 2. For all the languages		
+		for my $lang (@{$$self{'languages'}})
+		{
+			my $l = $$lang[LANG_NAME];
+			print $makefile_f "autogen_webapp/${webappname}Page${nm}_$l.cpp: $deps $html_files\n\t$page_cmd $l\n\n";
+			# run script
+			die "Could not generate page $nm, language $l" unless system("$page_cmd $l") == 0;
+		}
+	}
+	
+	# write line for the main application class
+	print $makefile_f "autogen_webapp/${webappname}Server.cpp autogen_webapp/${webappname}Server.h autogen_webapp/${webappname}Global.cpp autogen_webapp/${webappname}Global.h $language_files: $app_description ../../lib/webappframework/WebAppFramework.pm\n";
+	print $makefile_f "\tperl ../../lib/webappframework/WebApplication.pl $app_description make\n\n";
+}
+
+
+sub write_global_code()
+{
+	my ($self, $page) = @_;
+
+	my $webappname = $$self{'webappname'};
+
+	# Make header file
+	my $global_h = gensym;
+	open $global_h, ">autogen_webapp/${webappname}Global.h" or die "Can't open global h file for writing";
+	
+	write_h_boilerplate_begin($global_h, "${webappname}Global");
+	print $global_h $_ for ((map {"#include <$_>\n"} ('vector','string',$page->get_required_headers(WebAppFramework::Unit::HEADERS_GLOBAL_H_SYSTEM))),
+		(map {qq`#include "$_"\n`} $page->get_required_headers(WebAppFramework::Unit::HEADERS_GLOBAL_H_PROJECT)));
+
+	# write global .h file
+	{
+		my $output = WebAppFramework::Output->new($global_h, '_APP_GLOBAL');
+		$output->setup_languages('COMMON', $$self{'default_language'}, '_APP_GLOBAL');
+		$output->set_code_preprocessor($page);
+
+		# run through all the phases, writing output from all the units
+		for my $phase (WebAppFramework::Unit::PHASE_INITIALISE,
+			WebAppFramework::Unit::PHASE_GLOBAL_H__BEGIN .. WebAppFramework::Unit::PHASE_GLOBAL_H__END)
+		{
+			# get the page to write itself
+			$page->write($output, $phase, 0);
+		}
+
+		# write updated language files
+		$output->save_language_files();
+	}
+
+	# finish the .h file
+	write_h_boilerplate_end($global_h, "${webappname}Global");
+	close $global_h;
+
+	# make cpp file
+	my $global_cpp = gensym;
+	open $global_cpp, ">autogen_webapp/${webappname}Global.cpp" or die "Can't open global cpp file for writing";
+
+	write_cpp_boilerplate_begin($global_cpp, "${webappname}Global");
+	# write out the various headers
+	print $global_cpp qq!#include "${webappname}Global.h"\n\n#include "MemLeakFindOn.h"\n\n!;
+	
+	# Write the code for the cpp file
+	{
+		my $output = WebAppFramework::Output->new($global_cpp, '_APP_GLOBAL');
+		$output->setup_languages('COMMON', $$self{'default_language'}, '_APP_GLOBAL');
+		$output->set_code_preprocessor($page);
+
+		# run through all the phases, writing output from all the units
+		for my $phase (WebAppFramework::Unit::PHASE_INITIALISE,
+			WebAppFramework::Unit::PHASE_GLOBAL_CPP__BEGIN .. WebAppFramework::Unit::PHASE_GLOBAL_CPP__END)
+		{
+			# get the page to write itself
+			$page->write($output, $phase, 0);
+		}
+
+		# write updated language files
+		$output->save_language_files();
+	}
+	
+	close $global_cpp;
+}
+
+
+sub write_server_class
+{
+	my ($self) = @_;
+	
+	my $h = gensym;
+	my $cpp = gensym;
+	
+	my $classname = $$self{'webappname'}.'Server';
+	my $appclassname = $$self{'webappname'};
+	my $daemon_name = $$self{'daemon_name'};
+	my $url_base = $$self{'url_base'};
+
+	open $h,">autogen_webapp/$classname.h" or die "Can't open server h file";
+	open $cpp,">autogen_webapp/$classname.cpp" or die "Can't open server h file";
+	
+	# write header file
+	write_h_boilerplate_begin($h, $classname);
+	
+	print $h <<__E;
+#include <string>
+#include <vector>
+
+#include "WebApplication.h"
+#include "../$appclassname.h"
+
+class $classname : public WebApplication
+{
+public:
+	$classname();
+	~$classname();
+private:
+	// no copying
+	$classname(const $classname &);
+	$classname &operator=(const $classname &);
+public:
+
+	const char *DaemonName() const;
+
+	$appclassname &GetApplication() {return mApplication;}
+	virtual WebApplicationObject &GetApplicationObject();
+	virtual const char *GetURLBase() const;
+
+	bool HandlePage(const HTTPRequest &rRequest, HTTPResponse &rResponse,
+		uint32_t Language, uint32_t Page, std::vector<std::string> &rURLElements);
+	bool GetStaticFile(const char *URI, const void **ppFileOut,
+		int *pFileSizeOut, const char **ppFileMIMETypeOut);
+__E
+	if(exists $$self{'homepage'})
+	{
+		# add in function for homepage info
+		print $h <<__E;
+	bool GetHomePageURI(std::string &rHomePageLocation, bool &AsRedirect) const;
+__E
+	}
+
+	# add in handle functions for each of the pages for each of the languages
+	while(my ($nm,$pi) = each %{$$self{'pages'}})
+	{
+		for my $lang (@{$$self{'languages'}})
+		{
+			my $l = $$lang[LANG_NAME];
+			print $h "\tbool HandlePage${nm}_$l(const HTTPRequest &rRequest, HTTPResponse &rResponse, const std::vector<std::string> &rElements);\n";
+		}
+	}
+
+	if(exists $$self{'extra_config_directives'})
+	{
+		print $h <<__E;
+
+protected:
+	const ConfigurationVerify *GetConfigVerify() const;
+__E
+	}
+
+	print $h <<__E;
+
+private:
+	$appclassname mApplication;
+};
+
+__E
+	
+	write_h_boilerplate_end($h, $classname);
+
+	# write CPP file
+	write_cpp_boilerplate_begin($cpp, $classname);
+	print $cpp <<__E;
+#include <string.h>
+
+#include "$classname.h"
+#include "HTTPRequest.h"
+#include "HTTPResponse.h"
+
+#include "MemLeakFindOn.h"
+
+
+${classname}::$classname()
+{
+}
+${classname}::~$classname()
+{
+}
+const char *${classname}::DaemonName() const
+{
+	return "$daemon_name";
+}
+bool ${classname}::HandlePage(const HTTPRequest &rRequest, HTTPResponse &rResponse,
+		uint32_t Language, uint32_t Page, std::vector<std::string> &rURLElements)
+{
+	// Switch on language and page name to dispatch to handling function
+	switch(Language)
+	{
+__E
+	for my $lang_en (@{$$self{'languages'}})
+	{
+		my $l = $$lang_en[LANG_NAME];
+		my $l_v = element_to_string($l);
+		print $cpp <<__E;
+	case $l_v: // $l
+		{
+			switch(Page)
+			{
+__E
+		while(my ($nm,$pi) = each %{$$self{'pages'}})
+		{
+			my $ns = $$pi[PAGE_SHORT_NAME];
+			my $n_v = element_to_string($ns);
+
+			print $cpp <<__E;
+			case $n_v: // $ns ($nm)
+				return HandlePage${nm}_$l(rRequest, rResponse, rURLElements);
+				break;
+__E
+		}
+		print $cpp <<__E;
+			default:
+				break;
+			}
+		}
+		break;
+__E
+	}
+
+	print $cpp <<__E;
+	default:
+		break;
+	}
+	// not handled by default
+	return false;
+}
+const char *${classname}::GetURLBase() const
+{
+	return "$url_base";
+}
+WebApplicationObject &${classname}::GetApplicationObject()
+{
+	return mApplication;
+}
+__E
+	if(exists $$self{'extra_config_directives'})
+	{
+		# write a config directive function
+		print $cpp <<__E;		
+const ConfigurationVerify *${classname}::GetConfigVerify() const
+{
+	static ConfigurationVerifyKey verifyserverkeys[] = 
+	{
+		HTTPSERVER_VERIFY_SERVER_KEYS(0)	// no default addresses
+	};
+
+	static ConfigurationVerify verifyserver[] = 
+	{
+		{
+			"Server",
+			0,
+			verifyserverkeys,
+			ConfigTest_Exists | ConfigTest_LastEntry,
+			0
+		}
+	};
+	
+	static ConfigurationVerifyKey verifyrootkeys[] = 
+	{
+__E
+		for(@{$$self{'extra_config_directives'}})
+		{
+			my ($type,$name) = @$_;
+			my $e = ($type eq 'int')?'ConfigTest_IsInt | ':'';
+			print $cpp qq!\t\t{"$name", 0, ${e}ConfigTest_Exists, 0},\n!;
+		}
+
+		print $cpp <<__E;		
+		HTTPSERVER_VERIFY_ROOT_KEYS
+	};
+
+	static ConfigurationVerify verify =
+	{
+		"root",
+		verifyserver,
+		verifyrootkeys,
+		ConfigTest_Exists | ConfigTest_LastEntry,
+		0
+	};
+
+	return &verify;
+}
+__E
+	}
+	
+	if(exists $$self{'homepage'})
+	{
+		# add in function for homepage info
+		my ($type,$uri) = @{$$self{'homepage'}};
+		my $rdr = ($type eq 'redirect')?'true':'false';
+		print $cpp <<__E;
+bool ${classname}::GetHomePageURI(std::string &rHomePageLocation, bool &rAsRedirect) const
+{
+	rHomePageLocation = "$uri";
+	rAsRedirect = $rdr;
+	return true;
+}
+__E
+	}
+	# write any static files
+	$self->write_static_file_function($cpp);
+
+	close $cpp;
+	close $h;
+}
+
+sub write_page
+{
+	my ($self, $page, $page_name, $language) = @_;
+	
+	my $webappname = $$self{'webappname'};
+
+	# create headers in string...
+	my $include_headers = '';
+	# collect together system includes
+	my @system_headers = $page->get_required_headers(WebAppFramework::Unit::HEADERS_SYSTEM);
+	$include_headers .= "#include <$_>\n" for(@system_headers);
+	# include the header file for the main server class, the .h file for this page, and the various
+	# standard header files which will be required.
+	$include_headers .= <<__E;
+#include "${webappname}Server.h"
+#include "${webappname}Global.h"
+#include "${webappname}Page$page_name.h"
+#include "HTTPResponse.h"
+#include "HTTPRequest.h"
+__E
+	# collect together project includes
+	my @project_headers = $page->get_required_headers(WebAppFramework::Unit::HEADERS_PROJECT);
+	$include_headers .= "#include \"$_\"\n" for(@project_headers, 'Conversion.h', 'autogen_ConversionException.h');
+
+	# class which contains all the parameters
+	my $param_classname = "${webappname}Page${page_name}Parameters";
+
+	if($language eq 'COMMON')
+	{
+		# Write all the common code
+		
+		# first, generate a data class for the parameters
+		my $param_class = CppDataClass->new($param_classname, '', $page->get_parameters());
+		$param_class->set_option('ReadOnly', 1);
+		$param_class->add_declarations(CppDataClass::PUBLIC, 'bool SetFromURLElements(const std::vector<std::string> &rElements);');
+
+		# Make header file
+		my $main_h = gensym;
+		open $main_h, ">autogen_webapp/${webappname}Page$page_name.h" or die "Can't open main h file for $page_name";
+		
+		write_h_boilerplate_begin($main_h, "${webappname}Page$page_name");
+		print $main_h $_ for ((map {"#include <$_>\n"} ('vector','string',$page->get_required_headers(WebAppFramework::Unit::HEADERS_PAGE_H_SYSTEM))),
+			(map {qq`#include "$_"\n`} $page->get_required_headers(WebAppFramework::Unit::HEADERS_PAGE_H_PROJECT)));
+		print $main_h "class HTTPRequest;\nclass HTTPResponse;\n\n";
+		
+		{
+			# create an output object
+			my $output = WebAppFramework::Output->new($main_h, $page_name.'_COMMON');
+			$output->setup_languages('COMMON', $$self{'default_language'}, $page_name);
+			$output->set_code_preprocessor($page);
+
+			# write the list of defines for translated strings
+			$page->write_translated_strings($output, 1);	# 1 means "write defines"
+			
+			# write parameter class
+			$output->write_code($param_class->generate_h());
+			
+			# run through all the phases, writing output from all the units
+			for my $phase (WebAppFramework::Unit::PHASE_INITIALISE,
+				WebAppFramework::Unit::PHASE_MAIN_H__BEGIN .. WebAppFramework::Unit::PHASE_MAIN_H__END)
+			{
+				# get the page to write itself
+				$page->write($output, $phase, 0);
+			}
+
+			# write updated language files
+			$output->save_language_files();
+		}
+		
+		# finish the .h file
+		write_h_boilerplate_end($main_h, "${webappname}Page$page_name");
+		close $main_h;
+
+		# make cpp file
+		my $main_cpp = gensym;
+		open $main_cpp, ">autogen_webapp/${webappname}Page$page_name.cpp" or die "Can't open main cpp file for $page_name";
+
+		write_cpp_boilerplate_begin($main_cpp, "${webappname}Page$page_name");
+		# write out the various headers
+		print $main_cpp $include_headers;
+		print $main_cpp qq`\n#include "MemLeakFindOn.h"\n\n`;
+
+		# get units to write code
+		{
+			# create an output object
+			my $output = WebAppFramework::Output->new($main_cpp, $page_name.'_COMMON');
+			$output->setup_languages('COMMON', $$self{'default_language'}, $page_name);
+			$output->set_code_preprocessor($page);
+
+			# write parameter class
+			$output->write_code($param_class->generate_cpp());
+			
+			# write the function to set the parameters from the URL elements
+			my @params = $page->get_parameters();
+			my $element_n = URL_ELEMENTS_PARAMS_START;	# first element index to consider
+			my $required_elements_size = URL_ELEMENTS_PARAMS_START + $#params + 1;
+			$output->write_code(<<__E);
+bool ${param_classname}::SetFromURLElements(const std::vector<std::string> &rElements)
+{
+	if(rElements.size() != $required_elements_size)
+	{
+		return false;
+	}
+	try
+	{
+__E
+			# write code for each of the parameters
+			for my $p (@params)
+			{
+				# and and converted value
+				my $name = $p->name();
+				my $converted = $p->convert_from('std::string', "rElements[$element_n]");
+				
+				# write code
+				$output->write_code(<<__E);
+		m$name = $converted;
+__E
+				$element_n++;
+			}
+
+			$output->write_code(<<__E);
+	}
+	catch(ConversionException &e)
+	{
+		// If there was a problem converting any of the values, an exception will be thrown.
+		// Convert to simple "not valid" error return
+		return false;
+	}
+	
+	return true;
+}
+__E
+			# run through all the phases, writing output
+			for my $phase (WebAppFramework::Unit::PHASE_INITIALISE,
+				WebAppFramework::Unit::PHASE_MAIN_CPP__BEGIN .. WebAppFramework::Unit::PHASE_MAIN_CPP__END)
+			{
+				# get the page to write itself
+				$page->write($output, $phase, 0);
+			}
+
+			# write updated language files
+			$output->save_language_files();
+		}
+
+		close $main_cpp;
+	}
+	else
+	{
+		# get a locale object, either supplied by the author or automatically
+		my $locale;
+		if(exists ${$$self{'languages_locale_exception'}}{$language})
+		{
+			# use supplied locale
+			$locale = ${$$self{'languages_locale_exception'}}{$language}
+		}
+		else
+		{
+			# load the default locale object for this language
+			my $objtype = 'WebAppFramework::Locale::'.$language;
+			eval <<__E;
+				use $objtype;
+				\$locale = $objtype->new();
+__E
+			if($@)
+			{
+				die "Failed to automatically generate locale object for language $language\n(type is $objtype)\n";
+			}
+		}
+		# and tell the page about it.
+		$page->set_locale($locale);
+	
+		# write a language file
+		my $cpp = gensym;
+		open $cpp, ">autogen_webapp/${webappname}Page${page_name}_$language.cpp" or die "Can't open language $language cpp file for $page_name";
+		
+		# write the various bits of boilerplate
+		write_cpp_boilerplate_begin($cpp, "${webappname}Page${page_name}_$language");
+		print $cpp $include_headers;
+		print $cpp '#include "'.$locale->get_cpp_include_name()."\"\n";
+		print $cpp qq`\n#include "MemLeakFindOn.h"\n\n`;
+		
+		# dump the map of the page, for debugging
+		print $cpp "\n/*\n\n";
+		$page->dump_structure($cpp, 'root', 0);
+		print $cpp "\n*/\n\n";
+
+		# create an output object
+		my $output = WebAppFramework::Output->new($cpp, $page_name.'_'.$language);
+		$output->setup_languages($language, $$self{'default_language'}, $page_name);
+		$output->set_code_preprocessor($page);
+		
+		# write the char* list of translated strings
+		$page->write_translated_strings($output, 0);	# 1 means "write strings"
+
+		# set up the text which is output at the beginning of each phase
+		my @phase_prefix;
+		my $locale_cpp_classname = $locale->get_cpp_classname();
+		$phase_prefix[WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_VARS] = <<__E;
+bool ${webappname}Server::HandlePage${page_name}_$language(const HTTPRequest &rRequest, HTTPResponse &rResponse, const std::vector<std::string> &rElements)
+{
+	// Locale
+	$locale_cpp_classname locale;
+	// Decode the parameters
+	$param_classname params;
+	if(!params.SetFromURLElements(rElements))
+	{
+		return false;
+	}
+__E
+		$phase_prefix[WebAppFramework::Unit::PHASE_LANG_CPP_HANDLE_OUTPUT] = <<__E;
+	rResponse.SetResponseCode(HTTPResponse::Code_OK);
+	rResponse.SetContentType("text/html; charset=UTF-8");
+__E
+		$phase_prefix[WebAppFramework::Unit::PHASE_LANG_CPP_FINISH] = <<__E;
+	return true;
+}
+__E
+
+		# run through all the phases, writing output
+		for my $phase (WebAppFramework::Unit::PHASE_INITIALISE,
+			WebAppFramework::Unit::PHASE_LANG_CPP__BEGIN .. WebAppFramework::Unit::PHASE_LANG_CPP__END)
+		{
+			# write any prefix to this phase
+			$output->write_code($phase_prefix[$phase]);
+			
+			# get the page to write itself
+			$page->write($output, $phase, 0);
+			
+			# flush any text to be written
+			$output->flush_text();
+		}
+		
+		# write updated language files
+		$output->save_language_files();
+
+		close $cpp;
+	}
+}
+
+sub write_h_boilerplate_begin
+{
+	my ($f, $filename_base) = @_;
+	
+	my $hg = 'WEBAPPAUTOGEN_'.uc($filename_base).'__H';
+	
+	print $f <<__E;
+//
+// Automatically generated file, do not edit
+//
+#ifndef $hg
+#define $hg
+
+__E
+}
+sub write_h_boilerplate_end
+{
+	my ($f, $filename_base) = @_;
+	
+	my $hg = 'WEBAPPAUTOGEN_'.uc($filename_base).'__H';
+	
+	print $f <<__E;
+
+#endif // $hg
+
+__E
+}
+
+sub write_cpp_boilerplate_begin
+{
+	my ($f, $filename_base) = @_;
+	
+	my $hg = 'WEBAPPAUTOGEN_'.uc($filename_base).'__H';
+	
+	print $f <<__E;
+//
+// Automatically generated file, do not edit
+//
+#include "Box.h"
+
+__E
+}
+
+sub element_to_string
+{
+	my ($e) = @_;
+
+	my $s = '0x';
+	for(reverse split(//,substr($e, 0, 4)))
+	{
+		$s .= sprintf("%02x", unpack('C',$_))
+	}
+
+	return $s
+}
+
+# returns the base URL for a page, including the current language
+sub get_url_base_for_page
+{
+	my ($self, $pagename) = @_;
+	
+	die "Page $pagename not known" unless exists ${$$self{'pages'}}{$pagename};
+	
+	my $pi = ${$$self{'pages'}}{$pagename};
+	return '/'.$$self{'url_base'}.'/'.$$self{'current_language'}.'/'.$$pi[PAGE_SHORT_NAME]
+}
+
+sub get_url_base
+{
+	my ($self) = @_;
+	return '/'.$$self{'url_base'};
+}
+
+# static file handling
+# Add a mime type
+sub add_file_ext_to_mime_type
+{
+	my ($self, $ext, $mime_type) = @_;
+	$_file_ext_to_mime_type{lc($ext)} = $mime_type;
+}
+
+# Add a static directory
+sub add_static_directory
+{
+	my ($self, $dirname, $web_dirname) = @_;
+	if($dirname =~ m!(\A/|/\Z)!)
+	{
+		die "Static directory $dirname: URI $web_dirname starts or ends with '/'\n";
+	}
+	push @{$$self{'static_dirs'}}, [$dirname, $web_dirname];
+}
+
+# Write the static files
+sub write_static_file_function
+{
+	my ($self, $cpp) = @_;
+	my $webappname = $$self{'webappname'};
+
+	my $code = '';
+	my $file_num = 0;
+
+	# then, for each directory write out the static files
+	for my $d (@{$$self{'static_dirs'}})
+	{
+		my ($dirname, $web_dirname) = @$d;
+		my $dirbegins = ($web_dirname eq '')?'/':"/$web_dirname/";
+		my $dirbegins_len = length($dirbegins);
+		$code .= qq!\tif(::strncmp(URI, "$dirbegins", $dirbegins_len) == 0)\n\t{\n!;
+		
+		# now for each file, write a static variable declaration and an if statement
+		opendir DIR,$dirname or die "Can't open dir $dirname";
+		my @dir = readdir DIR;
+		closedir DIR;
+		for my $leaf (@dir)
+		{
+			next unless $leaf =~ m/\A(.+)\.(.+)\Z/;
+			my ($file,$name,$ext) = ("$dirname/$1.$2", "$1.$2", $2);
+			my $data_len = -s $file;
+			open FILE,$file or die "Can't open $file";
+			my $data;
+			read FILE,$data,$data_len;
+			close FILE;
+			
+			# declaration
+			print $cpp "static const char *StaticFile$file_num = ";
+			
+			# split up the file (not the best way of doing this, of course...)
+			my $first_line = 1;
+			for(split /(.{0,80})/s, $data)
+			{
+				next if $_ eq '';
+				
+				# line start
+				print $cpp (($first_line)?'"':" \\\n\t\t\"");
+				$first_line = 0;
+		
+				# Alter partial line to fit in a nice C string
+				# WARNING: Must not change the size of the string by these transforms!
+				my $t = $_;
+				$t =~ s/\\/\\\\/g;
+				$t =~ s/\n/\\n/g;
+				$t =~ s/\t/\\t/g;
+				$t =~ s/"/\\"/g;
+				$t =~ s/([\x00-\x1f\x7d-\xff])/'\x'.sprintf("%02x", ord($1))/eg;
+				$t =~ s/(\\x[0-9a-f][0-9a-f])([0-9a-fA-F])/$1.'""'.$2/eg;
+		
+				# output data
+				print $cpp $t,'"';	
+			}
+			print $cpp ";\n";
+			
+			# find MIME type
+			my $mime_type = $_file_ext_to_mime_type{lc($ext)};
+			die "Don't know about MIME type for file extension $ext, use add_file_ext_to_mime_type() to set\n"
+				if $mime_type eq '';
+			
+			# write the code to check for this file
+			$code .= <<__E;
+		if(::strcmp(URI + $dirbegins_len, "$name") == 0)
+		{
+			*ppFileOut = StaticFile$file_num;
+			*pFileSizeOut = $data_len;
+			*ppFileMIMETypeOut = "$mime_type";
+			return true;
+		}
+__E
+			
+			# increment file number
+			$file_num++;
+		}
+		
+		$code .= "\t}\n"; 
+	}
+
+	# write the actual function
+	print $cpp <<__E;
+bool ${webappname}Server::GetStaticFile(const char *URI, const void **ppFileOut, int *pFileSizeOut, const char **ppFileMIMETypeOut)
+{
+$code
+
+	// static file not found.
+	return false;
+}
+__E
+}
+
+1;
+

Added: box/features/codeforintegration/lib/webappframework/WebAppFrameworkException.txt
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebAppFrameworkException.txt	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebAppFrameworkException.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,8 @@
+EXCEPTION WebAppFramework 11
+
+Internal						0
+TranslatedStringsNotSet			1
+BadDateFormat					2
+BadTimeFormat					3
+FixedPointScaleTooLarge			4	ScaleDigits > WAFUTILITY_FIXEDPOINT_MAX_SCALE_DIGITS
+FixedPointOverflow				5

Added: box/features/codeforintegration/lib/webappframework/WebApplication.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebApplication.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebApplication.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,368 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WebApplication.cpp
+//		Purpose: Web application base class
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "WebApplication.h"
+#include "WebApplicationObject.h"
+#include "HTTPRequest.h"
+#include "HTTPResponse.h"
+#include "Utils.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::WebApplication()
+//		Purpose: Constructor
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+WebApplication::WebApplication()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::~WebApplication()
+//		Purpose: Desctructor
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+WebApplication::~WebApplication()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::SetupInInitialProcess()
+//		Purpose: Allow the derived object to do more setup
+//		Created: 20/12/04
+//
+// --------------------------------------------------------------------------
+void WebApplication::SetupInInitialProcess()
+{
+	// Base class.
+	HTTPServer::SetupInInitialProcess();
+
+	// Get the web app object to do more work
+	WebApplicationObject &webAppObj(GetApplicationObject());
+	webAppObj.ApplicationStart(GetConfiguration());
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::HTTPConnectionOpening()
+//		Purpose: Inform web app code of child process starting
+//		Created: 22/12/04
+//
+// --------------------------------------------------------------------------
+void WebApplication::HTTPConnectionOpening()
+{
+	WebApplicationObject &rappObject(GetApplicationObject());
+	rappObject.ChildStart(GetConfiguration());
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::Handle(const HTTPRequest &, HTTPResponse &)
+//		Purpose: Handle request
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+void WebApplication::Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse)
+{
+	// First check -- if the URL ends with a '/', then the request must be rejected,
+	// as it would stop the generated relative links working
+	std::string requestURI(rRequest.GetRequestURI());
+	if(requestURI.size() == 0 || (requestURI.size() == 1 && requestURI == "/"))
+	{
+		// Response is for home page
+		std::string newURI;
+		bool redirect = true;
+		if(GetHomePageURI(newURI, redirect))
+		{
+			if(redirect)
+			{
+				// Redirect the browser
+				rResponse.SetAsRedirect(newURI.c_str());
+				return;
+			}
+			else
+			{
+				// Replace the URI behind the scenes
+				requestURI = newURI;
+			}
+		}
+	}
+	if(requestURI[requestURI.size() - 1] == '/')
+	{
+		UnhandledResponse(rRequest, rResponse);
+		return;
+	}
+
+	// Split up URL
+	std::vector<std::string> urlElements;
+	SplitString(requestURI, '/', urlElements);
+
+	// Test that the URL is within the web app namespace, and that the
+	// elements are of the right size
+	if(urlElements.size() < 3 || urlElements[0] != GetURLBase()
+		|| urlElements[1].size() > 4 || urlElements[2].size() != 4)
+	{
+		// Not within the application base...
+		
+		// What if it's a static file?
+		const void *fileData = 0;
+		int fileLength = 0;
+		const char *fileType = 0;
+		if(GetStaticFile(requestURI.c_str(), &fileData, &fileLength, &fileType))
+		{
+			ASSERT(fileData != 0 && fileLength >= 0 && fileType != 0);
+			
+			// Set response for this static file
+			rResponse.SetResponseCode(HTTPResponse::Code_OK);
+			rResponse.SetContentType(fileType);
+			rResponse.Write(fileData, fileLength);
+			// Mark the response as not dynamic
+			rResponse.SetResponseIsDynamicContent(false);
+		}
+		else
+		{
+			// Return page not found response
+			rResponse.SetAsNotFound(rRequest.GetRequestURI().c_str());
+		}
+
+		return;
+	}
+	
+	// Get the language and page names
+	uint32_t language = FourCharStringToInt(urlElements[1].c_str());
+	uint32_t page = FourCharStringToInt(urlElements[2].c_str());
+	
+	// Tell the application object about this
+	WebApplicationObject &rappObject(GetApplicationObject());
+	rappObject.RequestStart(GetConfiguration());
+
+	// Get the drived class to handle the request (autogen code)
+	bool handled = HandlePage(rRequest, rResponse, language, page, urlElements);
+
+	// Default unhandled response
+	if(!handled)
+	{
+		UnhandledResponse(rRequest, rResponse);
+	}
+
+	rappObject.RequestFinish();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::HTTPConnectionClosing()
+//		Purpose: Inform web app code of child process ending
+//		Created: 22/12/04
+//
+// --------------------------------------------------------------------------
+void WebApplication::HTTPConnectionClosing()
+{
+	WebApplicationObject &rappObject(GetApplicationObject());
+	rappObject.ChildFinish();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::HandlePage(...)
+//		Purpose: Autogen classes override this. Returns true if the request
+//				 was handled.
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+bool WebApplication::HandlePage(const HTTPRequest &rRequest, HTTPResponse &rResponse,
+		uint32_t Language, uint32_t Page, std::vector<std::string> &rURLElements)
+{
+	return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::ElementToInt(const char *)
+//		Purpose: Returns a 32 bit int representing the page name from the
+//				 given element of the URL. Used for faster URL and form dispatch.
+//		Created: 7/4/04
+//
+// --------------------------------------------------------------------------
+uint32_t WebApplication::FourCharStringToInt(const char *Element)
+{
+	const uint8_t *e = (const uint8_t *)Element;
+	uint32_t i = 0;
+	if(e[0] != 0)
+	{
+		i = e[0];
+		if(e[1] != 0)
+		{
+			i |= e[1] << 8;
+			if(e[2] != 0)
+			{
+				i |= e[2] << 16;
+				if(e[3] != 0)
+				{
+					i |= e[3] << 24;
+				}
+			}
+		}
+	}
+
+	return i;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::UnhandledResponse(const HTTPRequest &, HTTPResponse &)
+//		Purpose: Reports an unhandled page to the user
+//		Created: 7/4/04
+//
+// --------------------------------------------------------------------------
+void WebApplication::UnhandledResponse(const HTTPRequest &rRequest, HTTPResponse &rResponse)
+{
+	rResponse.SetResponseCode(HTTPResponse::Code_OK);
+	rResponse.SetContentType("text/html");
+	
+	#define UNHANDLED_HTML_1 "<html><head><title>Invalid application request</title></head>\n<body><h1>Invalid application request</h1>\n<p>The URI <i>"
+	#define UNHANDLED_HTML_2 "</i> could not be handled as it does not correspond to a valid application request.</p></body></html>\n"
+	rResponse.Write(UNHANDLED_HTML_1, sizeof(UNHANDLED_HTML_1) - 1);
+	rResponse.WriteStringDefang(rRequest.GetRequestURI());
+	rResponse.Write(UNHANDLED_HTML_2, sizeof(UNHANDLED_HTML_2) - 1);
+	// Mark the response as not dynamic
+	rResponse.SetResponseIsDynamicContent(false);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::GetFormDataString(const HTTPRequest &, const std::string &)
+//		Purpose: Utility function to retrieve the first form data string from a
+//				 HTTPRequest. (Limitations: Only retrieves the first, ignoring all others,
+//				 and will return a blank string if it doesn't exist.)
+//		Created: 9/4/04
+//
+// --------------------------------------------------------------------------
+const std::string &WebApplication::GetFormDataString(const HTTPRequest &rRequest, const std::string &rKey)
+{
+	const HTTPRequest::Query_t &query(rRequest.GetQuery());
+	HTTPRequest::Query_t::const_iterator i(query.find(rKey));
+	if(i == query.end())
+	{
+		// Not found, return an empty string
+		static std::string emptyString;
+		return emptyString;
+	}
+	else
+	{
+		// At least one form entry exists, return it
+		return i->second;
+	}
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::GetApplicationObject()
+//		Purpose: Return the web application object
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+WebApplicationObject &WebApplication::GetApplicationObject()
+{
+	static WebApplicationObject defaultObj;
+	return defaultObj;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::GetURLBase()
+//		Purpose: Return the URL base of the application, that is, the first elements of the URI expected.
+//		Created: 20/5/04
+//
+// --------------------------------------------------------------------------
+const char *WebApplication::GetURLBase() const
+{
+	return "webapp";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::GetStaticFile(const char *, void **, int *, const char **)
+//		Purpose: Return details of a static file (if it exists). This default
+//				 implementation finds no files.
+//		Created: 7/12/04
+//
+// --------------------------------------------------------------------------
+bool WebApplication::GetStaticFile(const char *URI, const void **ppFileOut, int *pFileSizeOut, const char **ppFileMIMETypeOut)
+{
+	TRACE0("Default GetStaticFile() called\n");
+	return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::GetConfigurationVariable(const char *)
+//		Purpose: Utility function to get a named config variable from 
+//				 the root of the Configuration object.
+//		Created: 29/10/04
+//
+// --------------------------------------------------------------------------
+const std::string &WebApplication::GetConfigurationVariable(const char *ConfigVar)
+{
+	const Configuration &rconfig(GetConfiguration());
+	return rconfig.GetKeyValue(ConfigVar);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplication::GetHomePageURI(std::string &, bool &) const
+//		Purpose: Get home page. No home page set by default.
+//		Created: 9/1/05
+//
+// --------------------------------------------------------------------------
+bool WebApplication::GetHomePageURI(std::string &rHomePageLocation, bool &rAsRedirect) const
+{
+	return false;
+}
+
+
+
+

Added: box/features/codeforintegration/lib/webappframework/WebApplication.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebApplication.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebApplication.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,63 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WebApplication.h
+//		Purpose: Web application base class
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WEBAPPLICATION__H
+#define WEBAPPLICATION__H
+
+#include <string>
+#include <vector>
+
+// Include all the utility functions, so they're handily accessible
+#include "WAFUtilityFns.h"
+
+#include "HTTPServer.h"
+class WebApplicationObject;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WebApplication
+//		Purpose: Web application base class
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+class WebApplication : public HTTPServer
+{
+public:
+	WebApplication();
+	~WebApplication();
+private:
+	// no copying
+	WebApplication(const WebApplication &);
+	WebApplication &operator=(const WebApplication &);
+public:
+
+	virtual void SetupInInitialProcess();
+
+	virtual void HTTPConnectionOpening();
+	virtual void HTTPConnectionClosing();
+
+	void Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse);
+	virtual bool HandlePage(const HTTPRequest &rRequest, HTTPResponse &rResponse,
+		uint32_t Language, uint32_t Page, std::vector<std::string> &rURLElements);
+	virtual bool GetStaticFile(const char *URI, const void **ppFileOut,
+		int *pFileSizeOut, const char **ppFileMIMETypeOut);
+	virtual WebApplicationObject &GetApplicationObject();
+	virtual const char *GetURLBase() const;
+	virtual bool GetHomePageURI(std::string &rHomePageLocation, bool &rAsRedirect) const;
+
+	// Utility functions
+	static uint32_t FourCharStringToInt(const char *Element);
+	void UnhandledResponse(const HTTPRequest &rRequest, HTTPResponse &rResponse);
+	static const std::string &GetFormDataString(const HTTPRequest &rRequest, const std::string &rKey);
+	const std::string &GetConfigurationVariable(const char *ConfigVar);
+};
+
+#endif // WEBAPPLICATION__H
+

Added: box/features/codeforintegration/lib/webappframework/WebApplication.pl
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebApplication.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebApplication.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+use strict;
+use Symbol;
+use lib '../../lib/perl';
+use lib '../../lib/webappframework';
+use lib '../../lib/database';
+use WebAppFramework;
+use CppVariable;
+use vars qw/$webapp $page $language/;
+
+my ($app_description,$action,$page_name,$language_a) = @ARGV;
+die "Application description not specified, or not found\n" unless -e $app_description;
+die "Action not specified\n" unless $action ne '';
+$language = $language_a;
+$language = 'COMMON' if $language eq '';
+
+# Create the application object
+$webapp = WebAppFramework->new();
+
+# Pull the descripition of the application
+require $app_description;
+
+# create the autogen directory, if it doesn't exist
+if(!-d 'autogen_webapp')
+{
+	mkdir 'autogen_webapp',0744 or die "Can't create autogen directory\n";
+}
+
+# Perform the required action
+if($action eq 'make')
+{
+	# Write the makefile and generate the code for all the pages
+	print "Generating web application makefile and main server class...\n";
+	my $makefile = gensym;
+	open $makefile,">Makefile.webapp" or die "Can't open Makefile.webapp for writing\n";
+	
+	$webapp->write_makefile($makefile, $app_description);
+	
+	close $makefile;
+
+	$webapp->write_server_class();
+
+	# generate the global functions and definitions file
+	$page = setup_page();
+	$page->call_in_default_page();
+	$webapp->write_global_code($page);
+}
+elsif($action eq 'page')
+{
+	# Write the source files for the page
+	my $action = ($language eq 'COMMON')?'common code':"language $language";
+	print "Generating web application page $page_name ($action)...\n";
+	
+	# tell the web application about the language we're doing
+	$webapp->set_current_langage($language);
+	
+	# Check the page exists.
+	die "Page description script for $page_name not found.\n" unless -f "Pages/$page_name.pl";
+	
+	# Create the basic page
+	$page = setup_page();
+	$page->call_in_default_page();
+	
+	# Tell it about the web application
+	$webapp->initialise_page($page, $page_name);
+		
+	# Read in the page script
+	require "Pages/$page_name.pl";
+	
+	# Write it
+	$webapp->write_page($page, $page_name, $language);
+}
+else
+{
+	die "Unknown action '$action'\n";
+}
+
+


Property changes on: box/features/codeforintegration/lib/webappframework/WebApplication.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/webappframework/WebApplicationObject.cpp
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebApplicationObject.cpp	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebApplicationObject.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,108 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WebApplicationObject.cpp
+//		Purpose: Base class for web application ob
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "WebApplicationObject.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::WebApplicationObject()
+//		Purpose: Constructor
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+WebApplicationObject::WebApplicationObject()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::~WebApplicationObject()
+//		Purpose: Destructor
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+WebApplicationObject::~WebApplicationObject()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::ApplicationStart(const Configuration &)
+//		Purpose: Called when an application starts, just after the configuration
+//				 has been read by before the initial fork.
+//		Created: 20/12/04
+//
+// --------------------------------------------------------------------------
+void WebApplicationObject::ApplicationStart(const Configuration &rConfiguration)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::ChildStart(const Configuration &)
+//		Purpose: Called when an application child process is started
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+void WebApplicationObject::ChildStart(const Configuration &rConfiguration)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::RequestStart(const Configuration &)
+//		Purpose: Called when a request is started
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+void WebApplicationObject::RequestStart(const Configuration &rConfiguration)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::RequestFinish()
+//		Purpose: Called when a request is finished
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+void WebApplicationObject::RequestFinish()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    WebApplicationObject::ChildFinish()
+//		Purpose: Called when a child is exiting
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+void WebApplicationObject::ChildFinish()
+{
+}
+
+

Added: box/features/codeforintegration/lib/webappframework/WebApplicationObject.h
===================================================================
--- box/features/codeforintegration/lib/webappframework/WebApplicationObject.h	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/WebApplicationObject.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,43 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    WebApplicationObject.h
+//		Purpose: Base class for web application ob
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef WEBAPPLICATIONCOBJECT__H
+#define WEBAPPLICATIONCOBJECT__H
+
+class Configuration;
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    WebApplicationObject
+//		Purpose: Base class for web application objects (hold application state)
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+class WebApplicationObject
+{
+public:
+	WebApplicationObject();
+	virtual ~WebApplicationObject();
+private:
+	// no copying
+	WebApplicationObject(const WebApplicationObject &);
+	WebApplicationObject &operator=(const WebApplicationObject &);
+public:
+
+	// Interface
+	virtual void ApplicationStart(const Configuration &rConfiguration);
+	virtual void ChildStart(const Configuration &rConfiguration);
+	virtual void RequestStart(const Configuration &rConfiguration);
+	virtual void RequestFinish();
+	virtual void ChildFinish();
+};
+
+#endif // WEBAPPLICATIONCOBJECT__H
+

Added: box/features/codeforintegration/lib/webappframework/genstarterapp.pl
===================================================================
--- box/features/codeforintegration/lib/webappframework/genstarterapp.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/genstarterapp.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+use strict;
+use Symbol;
+
+my $template_file = 'lib/webappframework/StarterTemplate.txt';
+my $template = gensym;
+unless(open($template,$template_file))
+{
+	print "Cannot open template $template_file\nYou must run this script from the root of the distribution.\n";
+	exit(1);
+}
+
+my $dir = ask_qn('Directory to create application in? (eg bin/newwebapp)',
+	"Directory given must start with 'bin/' and not already exist", sub
+		{
+			my $d = $_[0];
+			return 0 unless $d =~ m`\Abin/\w+\Z`;
+			return 0 if -e $d;
+			1;
+		}
+	);
+my $appname = ask_qn('Long name of application? (eg NewWebApp)');
+my $shortappname = ask_qn('Daemon name of application? (eg newwebapp)');
+my $url_base = ask_qn('URL base? (eg app for /app/... URLs)');
+my $use_database = ask_qn('Include database support? (answer yes or no)', 'Enter yes or no', \&check_yesno) eq 'yes';
+
+# create the directory
+mkdir $dir,0755 or die "Can't create directory $dir\n";
+
+# setup flags
+my %if_flags;
+$if_flags{'database'} = 1 if $use_database;
+$if_flags{'no_database'} = 1 unless $use_database;
+
+my $output = gensym;
+my $output_enabled = 1;
+
+while(<$template>)
+{
+	s/APP_LONG_NAME/$appname/g;
+	s/APP_SHORT_NAME/$shortappname/g;
+	s/APP_URL_BASE/$url_base/g;
+
+	if(m/\A\*\*\*\s+(.+?)\n/)
+	{
+		# command
+		my @cmd = split /\s+/,$1;
+		if(!$output_enabled && $cmd[0] eq 'endif')
+		{
+			# enable output again
+			$output_enabled = 1
+		}
+		elsif($output_enabled)
+		{
+			# process other commands
+			if($cmd[0] eq 'file')
+			{
+				# open a file
+				close $output;
+				open $output,">$dir/".$cmd[1] or die "Can't open $cmd[1] for writing";
+			}
+			elsif($cmd[0] eq 'mkdir')
+			{
+				# make directory
+				mkdir "$dir/".$cmd[1] or die "Can't create directory $cmd[1]";
+			}
+			elsif($cmd[0] eq 'if')
+			{
+				# need this section?
+				$output_enabled = exists $if_flags{$cmd[1]}
+			}
+		}
+	}
+	else
+	{
+		print $output $_ if $output_enabled
+	}
+}
+
+close $output;
+close $template;
+
+my $db_inc = ($use_database)?' lib/database':'';
+my $db_setup;
+if($use_database)
+{
+	$db_setup = <<__E;
+
+    ./$shortappname create_db sqlite $shortappname.sqlite
+__E
+}
+
+print <<__E;
+
+Template created. Additional steps:
+
+1) Add this line to modules.txt
+$dir lib/webappframework$db_inc
+
+2) Configure the build system
+     ./configure
+
+3) Build the module
+     (cd $dir; make)
+(add RELEASE=1 to build the release version)
+
+4) Try it out
+     cd debug/$dir$db_setup
+     ./$shortappname ../../../$dir/$shortappname.conf
+and view
+     http://localhost:1080/$url_base/en/main
+in your web browser
+
+5) Kill with 
+     xargs kill < $shortappname.pid
+
+__E
+
+sub ask_qn
+{
+	my ($qn,$errmsg,$check) = @_;
+	my $answer;
+	while($answer eq '')
+	{
+		print $qn,"\n";
+		$answer = <STDIN>;
+		$answer =~ s/\A\s+//;
+		$answer =~ s/\s+\Z//;
+		if($answer eq '' || (ref($check) && !&$check($answer)))
+		{
+			$answer = '';
+			print "*** ERROR: ",(($errmsg ne '')?$errmsg:'Please enter a value'),"\n";
+		}
+	}
+	$answer;
+}
+
+sub check_yesno
+{
+	my $answer = lc($_[0]);
+	return $answer eq 'yes' || $answer eq 'no'
+}
+


Property changes on: box/features/codeforintegration/lib/webappframework/genstarterapp.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/webappframework/preprocesslangfortrans.pl
===================================================================
--- box/features/codeforintegration/lib/webappframework/preprocesslangfortrans.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/preprocesslangfortrans.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+use strict;
+
+my ($in,$out_lang,$out_choices) = @ARGV;
+if($in eq '' || $out_lang eq '' || $out_choices eq '')
+{
+	die "usage : preprocesslangfortrans.pl <lang file> <output lang file> <output choices file>\n";
+}
+
+open IN,$in or die "Can't open $in";
+open OUT,'>'.$out_lang or die "Can't open $out_lang for writing";
+open CHOICES,'>'.$out_choices or die "Can't open $out_choices for writing";
+
+print CHOICES <<__E;
+
+This file contains the lists of choices from the language file, $out_lang.
+
+For each line, translate the SECOND word, leaving the original as it stands.
+
+For example, change
+
+   Monday : Monday
+
+to
+
+   Monday : Lundi
+
+when you are translating that line.
+
+==========================================================
+__E
+
+while(<IN>)
+{
+	if(m/\A[^#>@=].*\|/)
+	{
+		# it's a choices line!
+		print OUT "---- DO NOT TRANSLATE THIS ENTRY, translate $out_choices instead ----\n";
+		
+		chomp;
+		my @choices = split /\|/,$_;
+		for(@choices)
+		{
+			print CHOICES "$_ : $_\n";
+		}
+	}
+	else
+	{
+		# copy line to output
+		print OUT
+	}
+}
+
+close CHOICES;
+close OUT;
+close IN;


Property changes on: box/features/codeforintegration/lib/webappframework/preprocesslangfortrans.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/lib/webappframework/processlangfromtrans.pl
===================================================================
--- box/features/codeforintegration/lib/webappframework/processlangfromtrans.pl	                        (rev 0)
+++ box/features/codeforintegration/lib/webappframework/processlangfromtrans.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+use strict;
+
+my ($in,$in_choices,$out_lang) = @ARGV;
+if($in eq '' || $in_choices eq '' || $out_lang eq '')
+{
+	die "usage : processlangfromtrans.pl <input lang file> <input choices file> <output lang file>\n";
+}
+
+open IN,$in or die "Can't open $in";
+open CHOICES,$in_choices or die "Can't open $in_choices for reading";
+open OUT,'>'.$out_lang or die "Can't open $out_lang for writing";
+
+# Read choices...
+# skip preamble
+while(<CHOICES>)
+{
+	last if m/\A==================/;
+}
+# read in entries
+my %choices;
+while(<CHOICES>)
+{
+	chomp;
+	next unless m/\A(.+?)\s+:\s+(.+?)\Z/;
+	$choices{$1} = $2;
+}
+
+# process the file
+my $last_choice_trans = '';
+while(<IN>)
+{
+	if(m/\A> .*\|/)
+	{
+		print OUT;
+		
+		# it's a choices line
+		my $l = $_;
+		chomp $l;
+		$l =~ s/\A> //;
+		my @choices = split /\|/,$l;
+		$last_choice_trans = '';
+		for my $c (@choices)
+		{
+			print "choice $c not translated in $l\n" unless exists $choices{$c};
+			$last_choice_trans .= '|' if $last_choice_trans ne '';
+			$last_choice_trans .= $choices{$c};
+		}
+	}
+	elsif(m/\A---- DO NOT TRANSLATE THIS ENTRY/)
+	{
+		# put the translated choices back
+		print OUT $last_choice_trans,"\n";
+	}
+	else
+	{
+		# copy line to output
+		print OUT
+	}
+}
+
+close CHOICES;
+close OUT;
+close IN;


Property changes on: box/features/codeforintegration/lib/webappframework/processlangfromtrans.pl
___________________________________________________________________
Name: svn:executable
   + *

Modified: box/features/codeforintegration/modules.txt
===================================================================
--- box/features/codeforintegration/modules.txt	2006-09-01 09:42:50 UTC (rev 925)
+++ box/features/codeforintegration/modules.txt	2006-09-01 10:20:43 UTC (rev 926)
@@ -57,3 +57,27 @@
 
 # END_IF_DISTRIBUTION
 
+# IF_DISTRIBUTION(boxwaf)
+
+# database interface
+lib/dbdriver
+lib/dbdrv_sqlite	lib/dbdriver	-lsqlite
+lib/dbdrv_mysql		lib/dbdriver	-lmysqlclient
+lib/dbdrv_postgresql		lib/dbdriver	-lpq	-lpgtypes
+# explicitly include the dbdriver module for when no drivers are compiled in
+lib/database		lib/dbdriver	lib/dbdrv_sqlite	lib/dbdrv_mysql	lib/dbdrv_postgresql
+test/database		lib/database
+
+# send emails
+lib/smtpclient		lib/server
+test/smtpclient		lib/smtpclient
+
+# web application system
+
+lib/httpserver		lib/server
+test/httpserver		lib/httpserver
+lib/webappframework	lib/httpserver
+test/webappframework	lib/webappframework	lib/database	lib/crypto
+
+# END_IF_DISTRIBUTION
+

Added: box/features/codeforintegration/test/database/Makefile.extra
===================================================================
--- box/features/codeforintegration/test/database/Makefile.extra	                        (rev 0)
+++ box/features/codeforintegration/test/database/Makefile.extra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,6 @@
+
+# AUTOGEN SEEDING
+autogen_db/testdb_schema.cpp:	testdb.schema
+	../../lib/database/makedbmake.pl .
+
+# include-makefile: Makefile.db

Added: box/features/codeforintegration/test/database/testdatabase.cpp
===================================================================
--- box/features/codeforintegration/test/database/testdatabase.cpp	                        (rev 0)
+++ box/features/codeforintegration/test/database/testdatabase.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,320 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    testdatabase.cpp
+//		Purpose: Test database driver library
+//		Created: 10/5/04
+//
+// --------------------------------------------------------------------------
+
+
+#include "Box.h"
+
+#include <string.h>
+#include <map>
+#include <string>
+
+#include "Test.h"
+// include this next one first, to check required headers are built in properly
+#include "autogen_db/testqueries.h"
+#include "DatabaseConnection.h"
+#include "DatabaseQueryGeneric.h"
+#include "DatabaseDriverRegistration.h"
+#include "autogen_db/testdb_schema.h"
+#include "autogen_db/testdatabase_query.h"
+#include "autogen_DatabaseException.h"
+
+#ifdef MODULE_lib_dbdrv_postgresql
+	// see notes in DbQueryPostgreSQL.h
+	#include "PostgreSQLOidTypes.h"
+#endif
+
+
+#include "MemLeakFindOn.h"
+
+char *insertTestString[] = {"a string", "pants'", "\"hello\"", "lampshade", 0};
+int insertTestInteger[] = {324234, 322, 4, 1};
+#define NUMBER_VALUES 4
+
+void test_zero_changes_from_select(const char *driver, DatabaseQuery &rQuery)
+{
+	// Work around a bug in sqlite...
+#ifdef PLATFORM_SQLITE3
+	if(::strcmp(driver, "sqlite") != 0)
+#endif
+	{
+		TEST_THAT(rQuery.GetNumberChanges() == 0);
+	}
+}
+
+
+void check_pg_oid_types(DatabaseConnection &rdb)
+{
+#ifdef MODULE_lib_dbdrv_postgresql
+	/*
+		(note that there is extra whitespace after the introductory and end lines)
+		SQL Query
+		[
+			Name: CheckOID
+			Statement: select oid,typname from pg_type;
+			Results: int OID, std::string Name
+		]
+	*/
+	CheckOID query(rdb);
+	query.Execute();
+	std::map<std::string,int> o;
+	while(query.Next())
+	{
+		o[query.GetName()] = query.GetOID();
+	}
+	bool all_postgresql_oid_types_correct = true;
+	#define CHECK_OID_VAL(name,value) {std::map<std::string,int>::iterator i(o.find(name)); if(i == o.end() || i->second != value) {all_postgresql_oid_types_correct = false;}}
+	CHECK_OID_VAL("int4", INT4OID);
+	CHECK_OID_VAL("int2", INT2OID);
+	CHECK_OID_VAL("text", TEXTOID);
+	CHECK_OID_VAL("name", NAMEOID);
+	CHECK_OID_VAL("varchar", VARCHAROID);
+	CHECK_OID_VAL("bpchar", BPCHAROID);
+	CHECK_OID_VAL("bool", BOOLOID);
+	CHECK_OID_VAL("char", CHAROID);
+	CHECK_OID_VAL("oid", OIDOID);
+	CHECK_OID_VAL("int8", INT8OID);
+	//CHECK_OID_VAL("", );
+	TEST_THAT(all_postgresql_oid_types_correct);
+	if(all_postgresql_oid_types_correct)
+	{
+		::printf("PostgreSQL OID types correct.\n");
+	}
+#endif
+}
+
+
+void test_database(const char *driver, const char *connectionstring)
+{
+	// Is the driver available?
+	if(!Database::DriverAvailable(driver))
+	{
+		::printf("Driver %s not available, skipping tests for that database\n", driver);
+		return;
+	}
+	::printf("Testing interface with driver %s...\n", driver);
+
+	DatabaseConnection db;
+	try
+	{
+		db.Connect(std::string(driver), std::string(connectionstring), 1000 /* timeout */);
+	}
+	catch(DatabaseException &e)
+	{
+		if(e.GetSubType() != DatabaseException::FailedToConnect) throw;
+		::printf("Failed to connect to database server with driver %s, skipping rest of test\n", driver);
+		bool failedToConnect = false;
+		TEST_THAT(failedToConnect);
+		return;
+	}
+
+	// Check name
+	TEST_THAT(::strcmp(driver, db.GetDriverName()) == 0);
+
+	// If postgresql, check OID ids
+	if(::strcmp(driver, "postgresql") == 0)
+	{
+		check_pg_oid_types(db);
+	}
+	
+	// Test string quoting
+	{
+		std::string quoted;
+		db.QuoteString("quotes", quoted);
+		TEST_THAT(quoted == "'quotes'");
+	}
+
+	// Create basic schema, using autogen function
+	testdb_Create(db);
+	// Insert some values
+	{
+		int last_insertid = -1;
+		DatabaseQueryGeneric insert(db, "INSERT INTO tTest1(fInteger,fString) VALUES($1,$2)");
+		for(int n = 0; n < NUMBER_VALUES; ++n)
+		{
+			insert.Execute("is", insertTestInteger[n], insertTestString[n]);
+			int iid = db.GetLastAutoIncrementValue("tTest1", "fID");
+			TEST_THAT(iid > last_insertid);
+			last_insertid = iid;
+		}
+	}
+	// Read them out in sorted order
+	{
+		DatabaseQueryGeneric read(db, "SELECT fInteger,fString FROM tTest1 ORDER BY fInteger");
+		read.Execute();
+		test_zero_changes_from_select(driver, read);
+		TEST_THAT(read.GetNumberRows() == NUMBER_VALUES);
+		TEST_THAT(read.GetNumberColumns() == 2);
+		int n = NUMBER_VALUES - 1;
+		while(read.Next())
+		{
+			//::printf("|%d|%s|\n", read.GetFieldInt(0), read.GetFieldString(1).c_str());
+			TEST_THAT(n >= 0);
+			TEST_THAT(read.GetFieldInt(0) == insertTestInteger[n]);
+			TEST_THAT(read.GetFieldString(1) == insertTestString[n]);
+			--n;
+		}
+		TEST_THAT(n == -1);
+	}
+	// Check single values work as expected
+	{
+		DatabaseQueryGeneric count(db, "SELECT COUNT(*) FROM tTest1");
+		count.Execute();
+		TEST_THAT(count.GetSingleValueInt() == NUMBER_VALUES);
+		// Check it works again (will have modified the data the first time around)
+		TEST_THAT(count.GetSingleValueInt() == NUMBER_VALUES);
+	}
+	{
+		DatabaseQueryGeneric count(db, "SELECT fString FROM tTest1 WHERE fInteger=4");
+		count.Execute();
+		TEST_THAT(count.GetSingleValueString() == "\"hello\"");
+		// Check it works again (will have modified the data the first time around)
+		TEST_THAT(count.GetSingleValueString() == "\"hello\"");
+	}
+	// Check update row counts are OK
+	{
+		DatabaseQueryGeneric update(db, "UPDATE tTest1 SET fInteger=(fInteger+1) WHERE fInteger>400");
+		update.Execute();
+		TEST_THAT(update.GetNumberChanges() == 1);
+		TEST_THAT(update.Next() == false);
+	}
+	// Try an autogenerated query
+	{
+	/*
+		(note that there is extra whitespace after the introductory and end lines)
+		SQL Query	
+		[	
+			Name: Test1
+			Statement: SELECT fInteger,fString FROM tTest1 WHERE fInteger>$1
+			Parameters: int
+			Results: int Integer, std::string String
+		]	
+	*/
+		Test1 query(db);
+		query.Execute(3);
+		int n = 0;
+		while(query.Next())
+		{
+			++n;
+		}
+		TEST_THAT(n == 3);
+		test_zero_changes_from_select(driver, query);
+	}
+	// And one which was autogenerated in another file
+	{
+		Test2 query(db);
+		std::string string("lampshade");
+		query.Execute(1, &string);
+		TEST_THAT(query.Next());
+		TEST_THAT(query.GetInteger() == 1);
+		TEST_THAT(query.GetString() == "lampshade");
+		TEST_THAT(!query.Next());
+		test_zero_changes_from_select(driver, query);
+	}
+	// And another which returns a single value
+	{
+		TEST_THAT(Test3::Do(db, 1) == "lampshade");
+	}
+	// A query, autogenerated, which takes the statement at runtime
+	{
+		TestRuntimeQuery query(db, "SELECT fInteger,fString FROM tTest1 WHERE fInteger=$1");
+		query.Execute("i", 1);
+		TEST_THAT(query.Next());
+		TEST_THAT(query.GetInteger() == 1);
+		TEST_THAT(query.GetString() == "lampshade");
+		TEST_THAT(!query.Next());
+		test_zero_changes_from_select(driver, query);
+	}
+	// And finally, a query which returns an insert value
+	{
+	/*
+		SQL Query
+		[
+			Name: Test4
+			Statement: INSERT INTO tTest1(fInteger,fString) VALUES($1,$2)
+			Parameters: int, std::string
+			AutoIncrementValue: tTest1 fID
+		]
+	*/
+		int id = Test4::Do(db, 56, "xx1");
+		int id2 = Test4::Do(db, 898, "pdfdd");
+		TEST_THAT(id2 > id);
+		{
+			Test4 query(db);
+			query.Execute(2938, "ajjd");
+			int id3 = query.InsertedValue();
+			TEST_THAT(id3 > id2);
+		}
+	}
+	// Drop the schema, using autogen function
+	testdb_Drop(db);
+}
+
+// Vendorisation test driver
+class VendorTest : public DatabaseDriver
+{
+public:
+	VendorTest() {}
+	~VendorTest() {}
+	virtual const char *GetDriverName() const {return "vendortest";}
+	virtual void Connect(const std::string &rConnectionString, int Timeout) {}
+	virtual DatabaseDrvQuery *Query() {return 0;}
+	virtual void Disconnect() {};
+	virtual void QuoteString(const char *pString, std::string &rStringQuotedOut) const {}
+	virtual int32_t GetLastAutoIncrementValue(const char *TableName, const char *ColumnName) {return 0;}
+	virtual const TranslateMap_t &GetGenericTranslations()
+	{
+		static DatabaseDriver::TranslateMap_t table;
+		const char *from[] = {"TEST", "ARGS2", "X1", "Z1", 0};
+		const char *to[] = {"OUTPUT", "GG[!0:!1:!0", "Y !01", "!0U", 0};
+		DATABASE_DRIVER_FILL_TRANSLATION_TABLE(table, from, to);
+		return table;
+	}
+};
+
+void test_vendorisation()
+{
+	VendorTest driver;
+	std::string o;
+	bool printres = false;
+	#define TEST_TRANS(from, shouldbe)								\
+		DatabaseQuery::TEST_VendoriseStatement(driver, from, o);	\
+		if(printres) {::printf("|%s|->|%s|\n", from, o.c_str()); }	\
+		TEST_THAT(o == shouldbe);
+	TEST_TRANS("0123TEST4567", "0123TEST4567");
+	TEST_TRANS("0123`TEST4567", "0123OUTPUT4567");
+	TEST_TRANS("0123`TEST", "0123OUTPUT");
+	TEST_TRANS("0123`X(ywy2yyy2)SS", "0123Y ywy2yyy21SS");
+	TEST_TRANS("0123`X(ywy2yyy2)", "0123Y ywy2yyy21");
+	TEST_TRANS("`X(ywy2yyy2)SS", "Y ywy2yyy21SS");
+	TEST_TRANS("`Z(sjs8u)SS", "sjs8uUSS");
+	TEST_TRANS("0123`ARGS(hy2, x23s)4567", "0123GG[hy2: x23s:hy24567");
+	TEST_TRANS("0123`ARGS(hy2, x23s)", "0123GG[hy2: x23s:hy2");
+	TEST_TRANS("0123`ARGS(, x23s)", "0123GG[: x23s:");
+	TEST_TRANS("0123`ARGS(hy2,)", "0123GG[hy2::hy2");
+}
+
+int test(int argc, const char *argv[])
+{
+	// Test vendorisation
+	test_vendorisation();
+
+	// How many drivers?
+	const char *driverList = 0;
+	int nDrivers = Database::DriverList(&driverList);
+	TEST_THAT(driverList != 0);
+	::printf("%d drivers available: %s\n", nDrivers, driverList);
+
+	// Test the same code on all databases
+	test_database("sqlite", "testfiles/testdb.sqlite");
+	test_database("mysql", "testdb:testuser:password");
+	test_database("postgresql", "dbname = test");
+
+	return 0;
+}
+

Added: box/features/codeforintegration/test/database/testdb.schema
===================================================================
--- box/features/codeforintegration/test/database/testdb.schema	                        (rev 0)
+++ box/features/codeforintegration/test/database/testdb.schema	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,18 @@
+
+# A comment
+
+CREATE TABLE tTest1 (
+	fID `AUTO_INCREMENT_INT,
+	fInteger INTEGER,
+	fString VARCHAR(32)
+);
+
+
+CREATE TABLE tTest2 (
+	fI1 INTEGER, 	# a comment within a query
+	fS2 VARCHAR(45)
+);
+
+INSERT INTO tTest2 (fI1,fS2) VALUES(234,'Test');
+INSERT INTO tTest2 (fI1,fS2) VALUES(223,'Two');
+

Added: box/features/codeforintegration/test/database/testextra
===================================================================
--- box/features/codeforintegration/test/database/testextra	                        (rev 0)
+++ box/features/codeforintegration/test/database/testextra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,2 @@
+rm -rf testfiles
+mkdir testfiles

Added: box/features/codeforintegration/test/database/testqueries.query
===================================================================
--- box/features/codeforintegration/test/database/testqueries.query	                        (rev 0)
+++ box/features/codeforintegration/test/database/testqueries.query	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,28 @@
+
+This file contains some test queries. Note that all text outside the query
+markers is ignored.
+
+SQL Query
+[
+	Name: Test2
+	Statement: SELECT fInteger,fString FROM tTest1 WHERE fInteger=$1 AND fString=$2
+	Parameters: int, std::string StringParam NULL?
+	Results: int Integer, std::string String NULL?
+]
+
+SQL Query
+[
+	Name: Test3
+	Statement: SELECT fString FROM tTest1 WHERE fInteger=$1
+	Parameters: int
+	Results: std::string String
+	Flags: SingleValue
+]
+
+SQL Query
+[
+	Name: TestRuntimeQuery
+	Statement: runtime
+	Results: int Integer, std::string String
+]
+

Added: box/features/codeforintegration/test/httpserver/testfiles/httpserver.conf
===================================================================
--- box/features/codeforintegration/test/httpserver/testfiles/httpserver.conf	                        (rev 0)
+++ box/features/codeforintegration/test/httpserver/testfiles/httpserver.conf	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,8 @@
+
+AddressPrefix = http://localhost:1080
+
+Server
+{
+	PidFile = testfiles/httpserver.pid
+	ListenAddresses = inet:localhost:1080
+}

Added: box/features/codeforintegration/test/httpserver/testfiles/testrequests.pl
===================================================================
--- box/features/codeforintegration/test/httpserver/testfiles/testrequests.pl	                        (rev 0)
+++ box/features/codeforintegration/test/httpserver/testfiles/testrequests.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+use strict;
+use LWP::UserAgent;
+
+my $url_base = 'http://localhost:1080';
+
+my $ua = LWP::UserAgent->new(env_proxy => 0, keep_alive => 1, timeout => 30);
+
+print "GET request...\n";
+
+my $response1 = $ua->get("$url_base/test-one/34/341s/234?p1=vOne&p2=vTwo");
+exit 1 unless $response1->is_success();
+
+my $content = $response1->content();
+
+check_url($content, '/test-one/34/341s/234');
+check_params($content, 'p1'=>'vOne','p2'=>'vTwo');
+
+print "POST request...\n";
+
+my %post = ('sdfgksjhdfsd'=>'dfvsiufy2e3434','sxciuhwf8723e4'=>'238947829334',
+			'&sfsfsfskfhs'=>'?hdkfjhsjfds','fdsf=sdf2342'=>'3984sajhksda');
+
+my $response2 = $ua->post("$url_base/tdskjhfsjdkhf2943734?p1=vOne&p2=vTwo", \%post);
+
+my $content2 = $response2->content();
+
+check_url($content2, '/tdskjhfsjdkhf2943734');
+check_params($content2, %post);
+
+print "HEAD request...\n";
+
+my $response3 = $ua->head("$url_base/tdskjhfsdfkjhs");
+
+if($response3->content() ne '')
+{
+	print "Content not zero length\n";
+	exit(1);
+}
+
+if($response3->code() != 200)
+{
+	print "Wrong response code\n";
+	exit(1);
+}
+
+print "Redirected GET request...\n";
+
+my $response4 = $ua->get("$url_base/redirect?key=value");
+exit 4 unless $response4->is_success();
+
+my $content4 = $response4->content();
+
+check_url($content4, '/redirected');
+check_params($content4);
+
+print "Cookie tests...\n";
+
+# from examples in specs
+test_cookies('CUSTOMER=WILE_E_COYOTE', 'CUSTOMER=WILE_E_COYOTE');
+test_cookies('CUSTOMER="WILE_E_COYOTE"; C2="pants"', 'CUSTOMER=WILE_E_COYOTE', 'C2=pants');
+test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001');
+test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001', 'SHIPPING=FEDEX');
+test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', 'Customer=WILE_E_COYOTE');
+test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" ',
+	'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001');
+test_cookies(qq!\$Version="1"; Customer="WILE_E_COYOTE"; \$Path="/acme";  Part_Number="Rocket_Launcher_0001"; \$Path="/acme";  Shipping="FedEx"; \t \$Path="/acme"!,
+	'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001', 'Shipping=FedEx');
+
+# test the server setting cookies in the UA
+require HTTP::Cookies;
+$ua->cookie_jar(HTTP::Cookies->new());
+$ua->get("$url_base/set-cookie");
+test_cookies('', 'SetByServer=Value1');
+
+sub test_cookies
+{
+	my ($c_str, @cookies) = @_;
+	test_cookies2($c_str, @cookies);
+	$c_str =~ s/;/,/g;
+	test_cookies2($c_str, @cookies);	
+}
+
+sub test_cookies2
+{
+	my ($c_str, @cookies) = @_;
+	my $r;
+	if($c_str ne '')
+	{
+		$r = $ua->get("$url_base/cookie", 'Cookie' => $c_str);
+	}
+	else
+	{
+		$r = $ua->get("$url_base/cookie");
+	}
+	my $c = $r->content();
+	for(@cookies)
+	{
+		unless($c =~ m/COOKIE:$_<br>/)
+		{
+			print "Cookie $_ not found\n";
+			exit(1);
+		}
+	}
+}
+
+
+sub check_url
+{
+	my ($c,$url) = @_;
+	unless($c =~ m~URI:</b> (.+?)</p>~)
+	{
+		print "URI not found\n";
+		exit(1);
+	}
+	if($url ne $1)
+	{
+		print "Wrong URI in content\n";
+		exit(1);
+	}
+}
+
+sub check_params
+{
+	my ($c,%params) = @_;
+
+	while($c =~ m/^PARAM:(.+)=(.+?)<br>/mg)
+	{
+		if($params{$1} ne $2)
+		{
+			print "$1=$2 not found in response\n";
+			exit(1);
+		}
+		delete $params{$1}
+	}
+	
+	my @k = keys %params;
+	if($#k != -1)
+	{
+		print "Didn't find all params\n";
+		exit(1);
+	}	
+}


Property changes on: box/features/codeforintegration/test/httpserver/testfiles/testrequests.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/test/httpserver/testhttpserver.cpp
===================================================================
--- box/features/codeforintegration/test/httpserver/testhttpserver.cpp	                        (rev 0)
+++ box/features/codeforintegration/test/httpserver/testhttpserver.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,139 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    testhttpserver.cpp
+//		Purpose: Test code for HTTP server class
+//		Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "Test.h"
+#include "HTTPServer.h"
+#include "HTTPRequest.h"
+#include "HTTPResponse.h"
+
+#include "MemLeakFindOn.h"
+
+class TestWebServer : public HTTPServer
+{
+public:
+	TestWebServer();
+	~TestWebServer();
+
+	virtual void Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse);
+
+};
+
+// Build a nice HTML response, so this can also be tested neatly in a browser
+void TestWebServer::Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse)
+{
+	// Test redirection mechanism
+	if(rRequest.GetRequestURI() == "/redirect")
+	{
+		rResponse.SetAsRedirect("/redirected");
+		return;
+	}
+
+	// Set a cookie?
+	if(rRequest.GetRequestURI() == "/set-cookie")
+	{
+		rResponse.SetCookie("SetByServer", "Value1");
+	}
+
+	#define DEFAULT_RESPONSE_1 "<html>\n<head><title>TEST SERVER RESPONSE</title></head>\n<body><h1>Test response</h1>\n<p><b>URI:</b> "
+	#define DEFAULT_RESPONSE_3 "</p>\n<p><b>Query string:</b> "
+	#define DEFAULT_RESPONSE_4 "</p>\n<p><b>Method:</b> "
+	#define DEFAULT_RESPONSE_5 "</p>\n<p><b>Decoded query:</b><br>"
+	#define DEFAULT_RESPONSE_6 "</p>\n<p><b>Content type:</b> "
+	#define DEFAULT_RESPONSE_7 "</p>\n<p><b>Content length:</b> "
+	#define DEFAULT_RESPONSE_8 "</p>\n<p><b>Cookies:</b><br>\n"
+	#define DEFAULT_RESPONSE_2 "</p>\n</body>\n</html>\n"
+
+	rResponse.SetResponseCode(HTTPResponse::Code_OK);
+	rResponse.SetContentType("text/html");
+	rResponse.Write(DEFAULT_RESPONSE_1, sizeof(DEFAULT_RESPONSE_1) - 1);
+	const std::string &ruri(rRequest.GetRequestURI());
+	rResponse.Write(ruri.c_str(), ruri.size());
+	rResponse.Write(DEFAULT_RESPONSE_3, sizeof(DEFAULT_RESPONSE_3) - 1);
+	const std::string &rquery(rRequest.GetQueryString());
+	rResponse.Write(rquery.c_str(), rquery.size());
+	rResponse.Write(DEFAULT_RESPONSE_4, sizeof(DEFAULT_RESPONSE_4) - 1);
+	{
+		const char *m = "????";
+		switch(rRequest.GetMethod())
+		{
+		case HTTPRequest::Method_GET: m = "GET "; break;
+		case HTTPRequest::Method_HEAD: m = "HEAD"; break;
+		case HTTPRequest::Method_POST: m = "POST"; break;
+		}
+		rResponse.Write(m, 4);
+	}
+	rResponse.Write(DEFAULT_RESPONSE_5, sizeof(DEFAULT_RESPONSE_5) - 1);
+	{
+		const HTTPRequest::Query_t &rquery(rRequest.GetQuery());
+		for(HTTPRequest::Query_t::const_iterator i(rquery.begin()); i != rquery.end(); ++i)
+		{
+			rResponse.Write("\nPARAM:", 7);
+			rResponse.Write(i->first.c_str(), i->first.size());
+			rResponse.Write("=", 1);
+			rResponse.Write(i->second.c_str(), i->second.size());
+			rResponse.Write("<br>\n", 4);
+		}
+	}
+	rResponse.Write(DEFAULT_RESPONSE_6, sizeof(DEFAULT_RESPONSE_6) - 1);
+	const std::string &rctype(rRequest.GetContentType());
+	rResponse.Write(rctype.c_str(), rctype.size());
+	rResponse.Write(DEFAULT_RESPONSE_7, sizeof(DEFAULT_RESPONSE_7) - 1);
+	{
+		char l[32];
+		rResponse.Write(l, ::sprintf(l, "%d", rRequest.GetContentLength()));
+	}
+	rResponse.Write(DEFAULT_RESPONSE_8, sizeof(DEFAULT_RESPONSE_8) - 1);
+	const HTTPRequest::CookieJar_t *pcookies = rRequest.GetCookies();
+	if(pcookies != 0)
+	{
+		HTTPRequest::CookieJar_t::const_iterator i(pcookies->begin());
+		for(; i != pcookies->end(); ++i)
+		{
+			char t[512];
+			rResponse.Write(t, ::sprintf(t, "COOKIE:%s=%s<br>\n", i->first.c_str(), i->second.c_str()));
+		}
+	}
+	rResponse.Write(DEFAULT_RESPONSE_2, sizeof(DEFAULT_RESPONSE_2) - 1);
+}
+
+
+
+TestWebServer::TestWebServer() {}
+TestWebServer::~TestWebServer() {}
+
+int test(int argc, const char *argv[])
+{
+	if(argc >= 2 && ::strcmp(argv[1], "server") == 0)
+	{
+		// Run a server
+		TestWebServer server;
+		return server.Main("doesnotexist", argc - 1, argv + 1);
+	}
+	
+	// Start the server
+	int pid = LaunchServer("./test server testfiles/httpserver.conf", "testfiles/httpserver.pid");
+	TEST_THAT(pid != -1 && pid != 0);
+	if(pid > 0)
+	{
+		// Run the request script
+		TEST_THAT(::system("perl testfiles/testrequests.pl") == 0);
+	
+		// Kill it
+		TEST_THAT(KillServer(pid));
+		TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
+	}
+
+	return 0;
+}
+

Added: box/features/codeforintegration/test/smtpclient/testsmtpclient.cpp
===================================================================
--- box/features/codeforintegration/test/smtpclient/testsmtpclient.cpp	                        (rev 0)
+++ box/features/codeforintegration/test/smtpclient/testsmtpclient.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,100 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    testsmtpclient.cpp
+//		Purpose: Test SMTPClient class
+//		Created: 28/10/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+
+#include "Test.h"
+#include "SMTPClient.h"
+#include "autogen_SMTPClientException.h"
+#include "MemBlockStream.h"
+#include "ValidateEmailAddress.h"
+
+#include "MemLeakFindOn.h"
+
+// validation of emails
+typedef struct
+{
+	const char *input;
+	bool lookup;
+	bool expected_result;
+	const char *expected_output;
+} email_valid_test_t;
+
+email_valid_test_t email_valid_test[] = 
+{
+	{"ben@example.co.uk", true, true, "ben@example.co.uk"},
+	{" ben @ example.c o.uk ", true, true, "ben@example.co.uk"},
+	{" ben@examplecouk", true, false, "ben@examplecouk"},
+	{"BEN@FLUFFY.co.uk", true, true, "BEN@example.co.uk"},
+	{"ben@example.-co.uk", false, false, "ben@example.-co.uk"},
+	{"ben@example@co.uk", false, false, "ben@example@co.uk"},
+	{"@example.co.uk", false, false, "@example.co.uk"},
+	{"ben@example.a-co.uk", false, true, "ben@example.a-co.uk"},
+	{"ben@example.*co.uk", false, false, "ben@example.*co.uk"},
+	{"ben@example,co.uk ", false, false, "ben@example,co.uk"},
+	{"ben@. ", false, false, "ben@"},
+	{"ben@example.nosuchtld", false, true, "ben@example.nosuchtld"},
+	{"ben@example.nosuchtld", true, false, "ben@example.nosuchtld"},
+	{"ben@ relay.example.co.uk", true, true, "ben@relay.example.co.uk"},
+	{0, false, false, 0}
+};
+
+
+int test(int argc, const char *argv[])
+{
+	const static char *message = 
+		"To: ben.summers@example.co.uk\n"\
+		"From: ben@example.co.uk\n"\
+		"Subject: Test email\n\n"\
+		"This is a test email.\n\n"\
+		"\r\n\r\n.\r\nLine after . on it's own\r\n"\
+		".Something\r\nEnd of message\n";
+
+	// Don't send lots of emails in automated test runs!
+	if(argc == 2 && ::strcmp(argv[1], "send") == 0)
+	{
+		SMTPClient smtp("INSERT-RELAY-HERE", "INSERT-HOSTNAME-HERE");
+		SMTPClient::SendEmail send(smtp, "ben@example.co.uk");
+		send.To("ben.summers@example.co.uk");
+		MemBlockStream messageStream(message, strlen(message));
+		send.Message(messageStream);
+	
+		SMTPClient::SendEmail send2(smtp, "ben@example.co.uk");
+		send2.To(std::string("ben.summers@example.co.uk"));
+		send2.To(std::string("postmaster@example.co.uk"));
+		MemBlockStream messageStream2(message, strlen(message));
+		send2.Message(messageStream2);
+	}
+	if(argc == 2 && ::strcmp(argv[1], "send2") == 0)
+	{
+		SMTPClient smtp("INSERT-RELAY-HERE", "INSERT-HOSTNAME-HERE");
+		smtp.SendMessage(std::string("ben@example.co.uk"),
+			std::string("ben.summers@example.co.uk"), std::string(message));
+	}
+	
+	// Email validity test
+	email_valid_test_t *evt = email_valid_test;
+	while(evt->input != 0)
+	{
+		std::string in(evt->input), out;
+		int res = ValidateEmailAddress(in, out, evt->lookup);
+		TRACE5("'%s' -> '%s', lookup = %s, valid = %s (expected = %s)\n",
+			in.c_str(), out.c_str(), (evt->lookup)?"true":"false", (res)?"true":"false", (evt->expected_result)?"true":"false");
+		TEST_THAT(res == evt->expected_result);
+		TEST_THAT(::strcmp(out.c_str(), evt->expected_output) == 0);
+
+		// Next test
+		++evt;
+	}
+	
+	return 0;
+}
+

Added: box/features/codeforintegration/test/webappframework/Makefile.extra
===================================================================
--- box/features/codeforintegration/test/webappframework/Makefile.extra	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Makefile.extra	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,13 @@
+
+# AUTOGEN SEEDING
+Makefile.webapp:	TestWebApp.pl
+	perl ../../lib/webappframework/WebApplication.pl TestWebApp.pl make
+
+# include-makefile: Makefile.webapp
+
+# AUTOGEN SEEDING
+autogen_db/testdb_schema.cpp:	testdb.schema
+	../../lib/database/makedbmake.pl .
+
+# include-makefile: Makefile.db
+

Added: box/features/codeforintegration/test/webappframework/Pages/DeleteEntry.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/DeleteEntry.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/DeleteEntry.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,32 @@
+use WebAppFramework::Unit::Database::ExecuteQuery;
+use Database::Query;
+
+$page->add_text('TITLE', 'Delete entry');
+
+# The query to fetch the info out of the database
+my $query = Database::Query->new(
+		'Name' => 'DeleteEntry',
+		'Statement' => 'DELETE FROM tItems WHERE fID=$1',
+		'Parameters' => 'int ID',
+		'Results' => 'int32_t ID,std::string String,int32_t Integer,int32_t Colour,std::string CreatedBy'
+	);
+
+# A unit which will make sure it's executed
+# Using the redirect to the listing page is a bit of a cheat, but will work
+# because the query will never return any results... There are more elegant ways
+# of doing this which involve custom code...
+my $execquery = WebAppFramework::Unit::Database::ExecuteQuery->new(
+		'Name' => 'entry',
+		'Query' => $query,
+		'Args' => [ID => 'params.EntryID'],
+		# if there's no data returned, redirect somewhere harmless
+		'RedirectToOnNoRow' => ['ListEntries']
+	);
+
+# add it to the end of the page as a post unit, so it gets executed last (in the setup phase, of course)
+$page->add_post_unit($execquery);
+
+# Put something onto the page to avoid warnings
+$page->add_unit('PAGE', WebAppFramework::Unit::RawHTML->new('HTML' => ''));
+
+1;

Added: box/features/codeforintegration/test/webappframework/Pages/DisplayEntry.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/DisplayEntry.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/DisplayEntry.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,51 @@
+use WebAppFramework::Unit::Database::ExecuteQuery;
+use WebAppFramework::Unit::TableContainer;
+use WebAppFramework::Unit::Variable;
+use Database::Query;
+
+$page->add_text('TITLE', 'Display entry');
+
+# The query to fetch the info out of the database
+my $query = Database::Query->new(
+		'Name' => 'DisplayEntryRetrieve',
+		'Statement' => 'SELECT fID,fString,fInteger,fColour,fCreatedBy FROM tItems WHERE fID=$1',
+		'Parameters' => 'int ID',
+		'Results' => 'int32_t ID,std::string String,int32_t Integer,int32_t Colour,std::string CreatedBy'
+	);
+
+# A unit which will make sure it's executed
+my $execquery = WebAppFramework::Unit::Database::ExecuteQuery->new(
+		'Name' => 'entry',
+		'Query' => $query,
+		'Args' => [ID => 'params.EntryID'],
+		# if there's no data returned, redirect somewhere harmless
+		'RedirectToOnNoRow' => ['Login']
+	);
+
+# add it to the end of the page as a post unit, so it gets executed last (in the setup phase, of course)
+$page->add_post_unit($execquery);
+
+# display the information in a table
+my $table = WebAppFramework::Unit::TableContainer->new('FragmentsName' => 'Table');
+
+# quick function to make things easier
+my $row = 0;
+sub add_en
+{
+	my ($label,$var) = @_;
+	$table->add_text('0_'.$row, $label);
+	$table->add_unit('1_'.$row, WebAppFramework::Unit::Variable->new('Variable' => $var));
+	$row++
+}
+
+# add all the data to the table, using the helper function just defined
+add_en('ID', 'entry.ID');
+add_en('A string', 'entry.String');
+add_en('An integer', 'entry.Integer');
+add_en('Colour', 'entry.Colour');
+add_en('Created by', 'entry.CreatedBy');
+
+# display the table on the page
+$page->add_unit('PAGE', $table);
+
+1;

Added: box/features/codeforintegration/test/webappframework/Pages/ListEntries.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/ListEntries.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/ListEntries.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,54 @@
+use WebAppFramework::Unit::Database::Table;
+use Database::Query;
+use WebAppFramework::Unit::ListOfLinks;
+
+$page->add_text('TITLE', 'Display entry');
+
+# The query to fetch the entire list out of the database
+my $query = Database::Query->new(
+		'Name' => 'ListEntriesRetrieve',
+		'Statement' => 'SELECT fID,fName,fString,fInteger,fColourName,fBoolean,fCreatedBy FROM tItems LEFT JOIN tColours ON tItems.fColour=tColours.fColourID ORDER BY fString',
+		'Results' => 'int32_t ID,std::string Name,std::string String,int32_t Integer,std::string Colour,int32_t Boolean,std::string CreatedBy'
+	);
+
+# Unit to display all the entries
+# NOTE: If the query above had parameters, we'd specify them with the 'Args' attribute
+# to this object, just like you do with a WebAppFramework::Unit::Database::ExecuteQuery object.
+my $list = WebAppFramework::Unit::Database::Table->new(
+		'Name' => 'entry',
+		'Query' => $query,
+		'HideFields' => ['ID']	# don't display the ID from the query
+	);
+
+# change heading text from the default
+$list->add_text('Colour_Heading', 'Item colour');
+
+# To change how a cell is displayed, add an Name_Display unit.
+# In this example, to change how ID was displayed, we'd add a unit for
+# 'ID_Display'. If that unit needed to know what the ID was, then the page
+# variable entry.ID would show this. Here's a trivial example, for a different
+# field, which results in exactly the same output as the default, both on the
+# page and in the generated C++ code.
+use WebAppFramework::Unit::Variable;
+$list->add_unit('CreatedBy_Display', WebAppFramework::Unit::Variable->new('Variable' => 'entry.CreatedBy'));
+
+# Could add a column with heading inbetween two columns like this:
+# use WebAppFramework::Unit::RawHTML;
+# $list->add_unit('Column_Before_Integer', WebAppFramework::Unit::RawHTML->new('HTML' => 'UNIT HERE'));
+# $list->add_unit('Column_Before_Integer_Heading', WebAppFramework::Unit::RawHTML->new('HTML' => 'Extra Heading'));
+
+# add some action links
+
+$list->add_unit('Column_Last', WebAppFramework::Unit::ListOfLinks->new(
+	'Links' => 
+		[
+			['Display', ['DisplayEntry', 'EntryID' => 'entry.ID']],
+			['Delete', ['DeleteEntry', 'EntryID' => 'entry.ID']],
+			['Edit', ['NewEntry', 'EditEntryID' => 'entry.ID']]
+		],
+	# add an arbitary HTML attribute
+	'class' => 'action'
+	));
+
+# display the table on the page
+$page->add_unit('PAGE', $list);

Added: box/features/codeforintegration/test/webappframework/Pages/Login.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/Login.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/Login.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,69 @@
+use WebAppFramework::Unit::Form;
+use WebAppFramework::Unit::Variable;
+use WebAppFramework::Unit::FormErrorDisplay;
+use WebAppFramework::Unit::Code;
+use WebAppFramework::Unit::SimpleContainer;
+use WebAppFramework::Unit::Database::Table;
+use Database::Query;
+
+$page->add_text('TITLE', 'Login');
+
+# Container for everything on the page
+my $container = WebAppFramework::Unit::SimpleContainer->new('Separator' => '<br>&nbsp<br>');
+# place the container on the page
+$page->add_unit('PAGE', $container);
+
+my $form = WebAppFramework::Unit::Form->new('FormName' => 'login', 'FormValidation' => 'simple',
+	'ArgsToValidate' => 'Application'); #, 'ExternalErrors' => 1);
+	# NOTE: Validation is performed in TestWebAppFormLogin::Validate(),
+$form_container = $form->make_container();
+$form_container->add_text_field('Username', 'Username', 'length(4,34)', 'Default' => 'params.Username',
+			'Size' => 26, 'MaxLength' => 30, 'Attributes' => 'class="css_style"');
+$form_container->add_text_field('Password', 'Password', 'external', 'Size' => 20, 'DisplayAsPassword' => 1,
+	'ValidationFailMsg' => 'Password incorrect');
+$form_container->add_submit_button('DefaultButton', 'Login');
+
+# find the Authenticate unit
+my $auth = $page->find_unit('Authenticate', 'Name' => 'Security');
+
+# Use it to generate a validation function for this form.
+# This will set the credentials, and redirect to the given page.
+$auth->set_validate_function_on_form($webapp, $form, 'Username', 'Password');
+
+# And to set the credentials when the form is submitted
+$auth->set_HandleSubmission_on_form($form, 'Username', 'Password',
+	['Main', 'Username' => 'login.Username', 'Value' => 'CONSTANT:1', 'Text' => 'CONSTANT:start']);
+
+
+# move the error display to after the form, just to demonstrate it's possible
+#$form->add_post_unit(WebAppFramework::Unit::FormErrorDisplay->new('Form' => $form));
+
+# insert form into the main page container
+$container->add_unit('0', $form);
+
+# --------
+# see version 1.7 of this file for alternate code for validation and authenticate form handling
+# --------
+
+# --------------------------------------------------------------------------------
+
+# Unit to display all the users
+my $users = WebAppFramework::Unit::Database::Table->new(
+		'Name' => 'users',
+		'Query' => Database::Query->new(
+				'Name' => 'ListUsersQuery',
+				'Statement' => 'runtime',	# don't need to do things this way, but just for a test...
+				'Results' => 'int32_t ID,std::string Username,std::string Password'
+			)
+	);
+$container->add_text('1', 'List of all users on system');
+$container->add_unit('2', $users);
+
+# use a runtime query, just to be perverse and test out that particular feature.
+$users->set('QueryCode' => <<__E);
+		ListUsersQuery users(mApplication.GetDatabaseConnection(), "SELECT fID,fUsername,fPassword FROM tUsers WHERE fUsername <> \$1 ORDER BY fUsername");
+		users.Execute("s", "very-secret-user");
+__E
+
+
+1;

Added: box/features/codeforintegration/test/webappframework/Pages/Logout.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/Logout.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/Logout.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,10 @@
+use WebAppFramework::Unit::Code;
+
+$page->add_text('TITLE', 'Log out');
+$page->add_text('PAGE', 'You have been logged out');
+
+my $logout = WebAppFramework::Unit::Code->new('Phase' => 'lang_prepare', 'Code' => <<__E);
+	rResponse.SetCookie("Credentials", "");
+__E
+# add anywhere
+$page->add_post_unit($logout);

Added: box/features/codeforintegration/test/webappframework/Pages/Main.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/Main.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/Main.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,115 @@
+use WebAppFramework::Unit::TableContainer;
+use WebAppFramework::Unit::Form;
+use WebAppFramework::Unit::Variable;
+use WebAppFramework::Unit::SimpleContainer;
+use WebAppFramework::Unit::Code;
+use WebAppFramework::Unit::ListOfLinks;
+use WebAppFramework::Unit::FixedPointNumber;
+
+$page->add_translated_string("Error1", "Error message one");
+$page->add_translated_string("Error2", "Error message two");
+
+$page->add_text('TITLE', 'Main page');
+
+# The fragments name could be set in the defaults, but in real use, several table
+# template would be included in the template file.
+my $table = WebAppFramework::Unit::TableContainer->new('FragmentsName' => 'Table');
+
+$table->add_text('0_0', 'Top left');
+$table->add_text('0_2_Highlight', 'Bottom left');
+$table->add_text('1_1', 'Centre');
+$table->add_text('2_0', 'Top right');
+$table->add_text('2_2', 'Bottom right')
+	->link_to('Main', 'Text' => 'params.UserLoginToken');
+
+my $form = WebAppFramework::Unit::Form->new('FormName' => 'widgets', 'FormValidation' => 'errorgen', 'ExternalErrors' => 1);
+$form_container = $form->make_container();
+$form_container->add_text_field('Text', 'Text field', 'length(4,34)', 'Default' => 'params.Username',
+			'Size' => 26, 'MaxLength' => 30, 'Attributes' => 'class="css_style"');
+$form_container->add_checkbox('Random', 'Be a bit more random');
+$form_container->add_number_field('NumField', 'A number', 'range(-2,302)', '-1', '', 'Size' => '15', 'BlankValue' => -12);
+$form_container->add_number_field('NumFieldFP', 'A fixed point number', 'range(-2.8,302.89)', '-1', '', 'Size' => '15',
+	'FixedPointScaleDigits' => 4, 'FixedPointDisplayDigits' => 2, 'BlankValue' => -12);
+$form_container->add_choice('Thingy', 'Colour of bathtub', 'White|Black|Purple|Blue', 'items', 'single', '');
+$form_container->add_choice('X2', 'Test?', 'Yes|No|Only on Tuesdays', 'select', 'single', 'CONSTANT:Yes');
+# make sure there are 9 choices to test the code for looping over C++ constants for long lists
+$form_container->add_choice('OneOrTwo', 'Choose one or two', 'Pineapple|Orange|Banana|Apple|Lemon|Boat|Plane|Pants|Trousers', 'select', 'choices(1,2)', '');
+$form_container->add_choice('EmitErrors', 'Emit errors', 'one|two', 'items', 'choices(,)', '');
+$form_container->add_submit_button('DefaultButton', 'Submit the form');
+
+
+my $cookie_form = WebAppFramework::Unit::Form->new('FormName' => 'cookieForm',
+	'HandleSubmission' => "WAF::SetCookie('TestCookie', 'cookieForm.CookieValue');\n");
+my $cform_container = $cookie_form->make_container();
+$cform_container->add_text_field('CookieValue', 'Value of cookie', 'length(1,32)', 'Default' => 'cookie.TestCookie');
+$cform_container->add_submit_button('DefaultButton', 'Change cookie');
+
+# move the error display to after the form, just to demonstrate it's possible
+#$form->add_post_unit(WebAppFramework::Unit::FormErrorDisplay->new('Form' => $form));
+
+# -------------------
+
+# build a form where some items are optional
+my $optformlinks = WebAppFramework::Unit::ListOfLinks->new(
+	'Links' => 
+		[
+			['All', ['Main', 'FormShow' => 'CONSTANT:0']],
+			['Not BC', ['Main', 'FormShow' => 'CONSTANT:1']],
+			['Not F-H (in container)', ['Main', 'FormShow' => 'CONSTANT:2']],
+		]);
+
+my $optform = WebAppFramework::Unit::Form->new('FormName' => 'formshow');
+# containers
+$optform_container2 = $optform->make_container();
+$optform_container = $optform->make_container();
+# fill containers
+for('A' .. 'E')
+{
+	$optform_container->add_text_field($_, $_, 'length(4,34)');
+}
+$optform_container->add_submit_button('DefaultButton', 'Submit the form');
+# another container
+for('F' .. 'H')
+{
+	$optform_container2->add_text_field($_, $_, 'length(4,34)');
+}
+
+# make things conditional
+$optform->conditional_items('params.GetFormShow() != 1', 'B' => undef, 'C' => 'not shown');
+$optform->conditional_items('params.GetFormShow() != 2', $optform_container2 => 'container was here<br>');
+
+# -------------------
+
+# put them all together on the page
+$page->add_unit('PAGE', join_units('<br>&nbsp<br>',
+	$form,
+	"NumField = {widgets.NumField}, NumFieldFP = {widgets.NumFieldFP} (raw, formatted is below)",
+	WebAppFramework::Unit::FixedPointNumber->new('Variable' => 'widgets.NumFieldFP',
+			'ScaleDigits' => 4, 'DisplayDigits' => 2),
+	$table,
+	$optformlinks,
+	$optform,
+	$cookie_form,
+	<<__E,
+	<p>This is a long paragraph of text.
+	Multiple lines.</p>
+__E
+	<<__E
+	*userid_2*<p>This is another long paragraph.
+	Multiple lines, and the text is given a user id for keying the translations.</p>
+__E
+	)
+);
+
+# test the other pseudo functions, and putting arbitary code into the output
+# (WAF::SetCookie used above)
+my $code = WebAppFramework::Unit::Code->new('Phase' => 'lang_prepare', 'Code' => <<__E);
+	{
+		std::string link(WAF::Link('DisplayEntry', 'EntryID' => 'widgets.NumField'));
+		std::string var(WAF::Var('widgets.Text'));
+		std::string var2((std::string)WAF::Var('widgets.NumField'));
+	}
+__E
+$page->add_pre_unit($code);
+
+1;

Added: box/features/codeforintegration/test/webappframework/Pages/NewEntry.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/Pages/NewEntry.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Pages/NewEntry.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,92 @@
+use WebAppFramework::Unit::Form;
+use WebAppFramework::Unit::Database::OnSubmitExecSQL;
+use WebAppFramework::Unit::Database::FormNewOrEdit;
+use WebAppFramework::Unit::DataSource::DatabaseQuery;
+use WebAppFramework::Unit::OutputIf;
+
+# Now this can either create a new or edit an entry
+
+
+$page->add_text('TITLE', 'New/edit entry');
+
+# Create a form (with error validation)
+my $form = WebAppFramework::Unit::Form->new('FormName' => 'newentry');
+$form_container = $form->make_container();
+$form_container->add_text_field('Name', 'Name of item', 'length(4,34)');
+$form_container->add_text_field('String', 'A string', 'length(4,34)');
+$form_container->add_number_field('Integer', 'An integer', 'range(1,)');
+$form_container->add_checkbox('Boolean', 'Boolean flag');
+
+# create a data source to extract the colours from the database
+my $colours = WebAppFramework::Unit::DataSource::DatabaseQuery->new(
+		Query => {'Name' => 'GetColours',
+					'Statement' => 'SELECT fColourID,fColourName FROM tColours ORDER BY fColourID',
+					'Results' => 'int32_t ColourID,std::string ColourName'
+				}
+	);
+$form_container->add_choice('Colour', 'Colour', $colours, 'items', 'single', '');
+$form_container->add_submit_button('DefaultButton', 'Add entry');
+# Find the button, and make the label conditional on the type of form
+my $button = $form->find_unit('SubmitButton', 'Name'=>'DefaultButton');
+$button->add_unit('Label', WebAppFramework::Unit::OutputIf->new('Condition' => 'params.GetEditEntryID() == 0',
+	'@true' => 'Add new entry',
+	'@false' => 'Save changes'));
+
+
+if(0)
+{
+	# a test of the ability to use strings as keys for selections
+	my $colours2 = WebAppFramework::Unit::DataSource::DatabaseQuery->new(
+			Query => {'Name' => 'GetColours2',
+						'Statement' => 'SELECT fColourName,fColourID FROM tColours ORDER BY fColourName',
+						'Results' => 'std::string ColourName,int32_t ColourID'
+					}
+		);
+	$form_container->add_choice('ColourID', 'ColourID', $colours2, 'select', 'single', '');
+	$form->add_post_unit(WebAppFramework::Unit::Variable->new('Variable' => 'newentry.ColourID'));
+}
+
+
+
+
+
+my $new_or_edit = WebAppFramework::Unit::Database::FormNewOrEdit->new(
+	'@form' => $form,
+	'NewCondition' => 'params.GetEditEntryID() == 0',
+	'QueryNew' => {
+		'Name' => 'NewEntryInsert',
+		'Statement' => 'INSERT INTO tItems(fName,fString,fInteger,fColour,fBoolean,fCreatedBy) VALUES($1,$2,$3,$4,$5,$6)',
+		'Parameters' => 'std::string Name,std::string String,int32_t Integer,int32_t Colour,int32_t Boolean,std::string CreatedBy',
+#		'AutoIncrementValue' => 'tItems fID'
+	},
+	'ArgsNew' => ['CreatedBy' => 'params.Username'],
+	# be perverse, and use two statements to read the data out
+	'QueryRead' => [
+		{
+			'Statement' => 'SELECT fString,fInteger FROM tItems WHERE fID=$1',
+			'Parameters' => 'int32_t EntryID',
+			'Results' => 'std::string String,int32_t Integer',
+		},
+		{
+			'Statement' => 'SELECT fColour,fName,fBoolean FROM tItems WHERE fID=$1',
+			'Parameters' => 'int32_t EntryID',
+			'Results' => 'int32_t Colour,std::string Name,int32_t Boolean',
+		}
+	],
+	'ArgsRead' => ['EntryID' => 'params.EditEntryID'],
+	'ReadQueryNamespace' => 'original',		# make the results of these queries available as page vars
+	'RedirectOnNoReadResults' => ['ListEntries'],
+	'QueryUpdate' => {
+		'Statement' => 'UPDATE tItems SET fString=$1,fInteger=$2,fColour=$3,fCreatedBy=$4,fBoolean=$5 WHERE fID=$6',
+		'Parameters' => 'std::string String,int32_t Integer,int32_t Colour,std::string CreatedBy,int32_t Boolean,int32_t EntryID'
+	},
+	'ArgsUpdate' => ['EntryID' => 'params.EditEntryID', 'CreatedBy' => 'params.Username'],
+	'RedirectTo' => ['ListEntries'],
+	# Don't display the (uneditable) name field in edit mode
+	'ItemOverrideForUpdate' => ['Name' => '{original.Name}']
+);
+
+$page->add_unit('PAGE', $new_or_edit);
+
+
+1;

Added: box/features/codeforintegration/test/webappframework/Templates/TestWebAppMain.en.html
===================================================================
--- box/features/codeforintegration/test/webappframework/Templates/TestWebAppMain.en.html	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/Templates/TestWebAppMain.en.html	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,151 @@
+<html>
+<head>
+<title>###TITLE### -- <!--T-->Test Web Application<!--T--></title>
+</head>
+<body>
+<table border=0>
+<tr><td rowspan=2 valign="top">
+###MENU###
+<!--PageTemplate-Omit-Begin-->
+
+<!--Menu-Begin-->
+<table cellpadding=1 cellspacing=0 border=1>
+<tr><td><b>MENU</b></td></tr>
+<!--Menu/-->
+<!--Menu-Item-->
+<tr><td><a href="[URL]">[TEXT]</td></tr>
+<!--Menu/-->
+<!--Menu-ItemThisPage-->
+<tr><td>[TEXT]</td></tr>
+<!--Menu/-->
+<!--Menu-End-->
+</table>
+<!--Menu/-->
+
+<!--PageTemplate-Omit-End-->
+</td>
+<td>
+###PAGE###
+<!--PageTemplate-Omit-Begin-->
+<!--Table-Begin-->
+<table cellpadding=1 cellspacing=2 border=1>
+<!--Table/-->
+<!--Table-RowBegin-->
+<tr>
+<!--Table/-->
+<!--Table-CellBegin-->
+<td valign=top>
+<!--Table/-->
+<!--Table-CellEnd-->
+</td>
+<!--Table/-->
+<!--Table-CellBegin-Highlight-->
+<td valign=top bgcolor="#ff00ff">
+<!--Table/-->
+<!--Table-CellEnd-Highlight-->
+</td>
+<!--Table/-->
+<!--Table-RowEnd-->
+</tr>
+<!--Table/-->
+<tr>
+<!--Table-EmptyCell-->
+<td>&nbsp;</td>
+<!--Table/-->
+</tr>
+<!--Table-End-->
+</table>
+<!--Table/-->
+
+<!--Form-ErrorStart-->
+<font color=red>ERROR</font><br>
+<!--T-->Please correct the highlighted fields in the form below.<!--T--><p>
+<!--Form/-->
+
+<!--Form-ErrorListStart-->
+<ul><li>
+<!--Form/-->
+<!--Form-ErrorListSeparate-->
+<li>
+<!--Form/-->
+<!--Form-ErrorListEnd-->
+</ul>
+<!--Form/-->
+
+<!--Form-Begin-->
+<table cellpadding=1 cellspacing=2 border=0>
+<!--Form/-->
+<!--Form-RowBegin-->
+<tr>
+<!--Form/-->
+<!--Form-CellBegin-Label-->
+<td valign=top align=right>
+<!--Form/-->
+<!--Form-CellEnd-Label-->
+</td>
+<!--Form/-->
+<!--Form-CellBegin-->
+<td valign=top>
+<!--Form/-->
+<!--Form-ErrorMarker-->
+<font color="red">**</font>
+<!--Form/-->
+<!--Form-InlineErrorStart-->
+<br><small>
+<!--Form/-->
+Inline error message
+<!--Form-InlineErrorEnd-->
+</small>
+<!--Form/-->
+<!--Form-CellEnd-->
+</td>
+<!--Form/-->
+<!--Form-RowEnd-->
+</tr>
+<!--Form/-->
+<tr>
+<!--Form-EmptyCell-->
+<td>&nbsp;</td>
+<!--Form/-->
+</tr>
+<!--Form-End-->
+</table>
+<!--Form/-->
+
+<!--DatabaseTable-Begin-->
+<table cellpadding=1 cellspacing=2 border=1>
+<!--DatabaseTable/-->
+<!--DatabaseTable-RowBegin-->
+<tr>
+<!--DatabaseTable/-->
+<!--DatabaseTable-HeadingCellBegin-->
+<td valign=top bgcolor="#dddddd">
+<!--DatabaseTable/-->
+Heading
+<!--DatabaseTable-HeadingCellEnd-->
+</td>
+<!--DatabaseTable/-->
+<!--DatabaseTable-DataCellBegin-->
+<td valign=top>
+<!--DatabaseTable/-->
+Data
+<!--DatabaseTable-DataCellEnd-->
+</td>
+<!--DatabaseTable/-->
+<!--DatabaseTable-RowEnd-->
+</tr>
+<!--DatabaseTable/-->
+<!--DatabaseTable-End-->
+</table>
+<!--DatabaseTable/-->
+
+<!--PageTemplate-Omit-End-->
+</td></tr>
+<tr><td>
+###STATUS###
+</td></tr>
+</table>
+<hr>
+<!--T-->Test web application<!--T-->
+</body>
+</html>

Added: box/features/codeforintegration/test/webappframework/TestWebApp.cpp
===================================================================
--- box/features/codeforintegration/test/webappframework/TestWebApp.cpp	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/TestWebApp.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,73 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    TestWebApp.cpp
+//		Purpose: Test web application
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "TestWebApp.h"
+#include "autogen_webapp/TestWebAppPageLogin.h"
+#include "Configuration.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    TestWebApp::TestWebApp()
+//		Purpose: Constructor
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+TestWebApp::TestWebApp()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    TestWebApp::~TestWebApp()
+//		Purpose: Desctructor
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+TestWebApp::~TestWebApp()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    TestWebApp::ChildStart(const Configuration &)
+//		Purpose: Called when a child is started
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+void TestWebApp::ChildStart(const Configuration &rConfiguration)
+{
+	mDatabase.Connect(rConfiguration.GetKeyValue("DatabaseDriver"),
+		rConfiguration.GetKeyValue("DatabaseConnection"), 2000 /* timeout */);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    TestWebApp::ChildFinish()
+//		Purpose: Called when the child process ends
+//		Created: 17/5/04
+//
+// --------------------------------------------------------------------------
+void TestWebApp::ChildFinish()
+{
+	mDatabase.Disconnect();
+}
+
+

Added: box/features/codeforintegration/test/webappframework/TestWebApp.h
===================================================================
--- box/features/codeforintegration/test/webappframework/TestWebApp.h	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/TestWebApp.h	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,40 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    TestWebApp.h
+//		Purpose: Test web application
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef TESTWEBAPP__H
+#define TESTWEBAPP__H
+
+#include "WebApplicationObject.h"
+#include "DatabaseConnection.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+//		Name:    TestWebApp
+//		Purpose: Test web application
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+class TestWebApp : public WebApplicationObject
+{
+public:
+	TestWebApp();
+	~TestWebApp();
+
+	void ChildStart(const Configuration &rConfiguration);
+	void ChildFinish();
+
+	DatabaseConnection &GetDatabaseConnection() {return mDatabase;}
+
+private:
+	DatabaseConnection mDatabase;
+};
+
+#endif // TESTWEBAPP__H
+

Added: box/features/codeforintegration/test/webappframework/TestWebApp.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/TestWebApp.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/TestWebApp.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,100 @@
+use WebAppFramework::Unit::PageTemplate;
+use WebAppFramework::Unit::TableContainer;
+use WebAppFramework::Unit::Variable;
+use WebAppFramework::Unit::Form;
+use WebAppFramework::Unit::Menu;
+use WebAppFramework::Unit::Database::Table;
+use WebAppFramework::Unit::Database::Authenticate;
+use WebAppFramework::Unit::OutputIf;
+use WebAppFramework::Locale::en;
+
+$webapp->set_webapp_name('TestWebApp', 'testwebapp', 'test');
+
+# Add some extra parameters in the config file
+$webapp->add_extra_config_directive('string', 'DatabaseDriver');
+$webapp->add_extra_config_directive('string', 'DatabaseConnection');
+
+# for the secret for MD5 hashing of the user's password...
+$webapp->add_extra_config_directive('string', 'CredentialTokenSecret');
+
+
+$webapp->add_language('en');
+$webapp->add_language('CAPS', WebAppFramework::Locale::en->new());
+$webapp->set_default_langage('en');
+
+# set up global parameters for all pages
+$webapp->add_global_parameters(cppvar('std::string','Username'),'int32_t UserLoginToken 0');
+# $webapp->add_global_parameters(cppvar('std::string','Credentials',''));
+
+# define each page, and the additional parameters it has
+$webapp->add_page('Login', 'lgin');
+$webapp->add_page('Main', 'main', 'int32_t Value', 'std::string Text', 'int32_t FormShow 0');
+$webapp->add_page('NewEntry', 'newe', 'int32_t EditEntryID 0');
+$webapp->add_page('DisplayEntry', 'disp', 'int32_t EntryID');
+$webapp->add_page('DeleteEntry', 'dele', 'int32_t EntryID');
+$webapp->add_page('ListEntries', 'list');
+$webapp->add_page('Logout', 'lgou');
+
+# set defaults for various Units
+WebAppFramework::Unit::TableContainer->set_defaults('Template' => 'TestWebAppMain');
+WebAppFramework::Unit::PageTemplate->set_defaults('Template' => 'TestWebAppMain');
+WebAppFramework::Unit::Form->set_defaults('Template' => 'TestWebAppMain', 'FragmentsName' => 'Form');
+WebAppFramework::Unit::FormTableContainer->set_defaults('Template' => 'TestWebAppMain', 'FragmentsName' => 'Form');
+WebAppFramework::Unit::Database::Table->set_defaults('Template' => 'TestWebAppMain', 'FragmentsName' => 'DatabaseTable');
+
+# Subroutine to set up the basics of the page
+sub setup_page
+{
+	my $page = WebAppFramework::Unit::PageTemplate->new(); # name of template set in defaults
+	
+	# security
+	my $security = WebAppFramework::Unit::Database::Authenticate->new('Name' => 'Security',
+		'Query' => {'Statement' => 'SELECT fID,fUsername,fPassword FROM tUsers WHERE fUsername = $1', # AND fPassword = $2',
+					'Parameters' => 'std::string Username', #, std::string Password',
+					'Results' => 'int ID, std::string Username, std::string Password'},
+		'TokenColumn' => 'Password',
+		'CredentialsSource' =>  'cookie.Credentials', #'params.Credentials',
+		'TokenFilter' => 'MD5',
+		'MD5SecretConfigVar' => 'CredentialTokenSecret',
+		'RedirectToOnAuthFailure' => ['Login'],
+		'DisableRedirectOnPages' => 'Login');
+	
+	$page->add_pre_unit($security);
+	
+	# default page contents
+	my $menu = WebAppFramework::Unit::Menu->new('Template' => 'TestWebAppMain', 'FragmentsName' => 'Menu',
+		Items => [
+				['Login', ['Login']],
+				['Main page', ['Main', 'Value' => 'CONSTANT:99', 'Text' => 'CONSTANT:SomeText']],
+				['New entry', ['NewEntry']],
+				['List entries', ['ListEntries']],
+				['Logout', ['Logout']]
+			]);
+	$page->add_unit('MENU', $menu);
+	
+	# status box
+	my $status = WebAppFramework::Unit::TableContainer->new('FragmentsName' => 'Table');
+	$status->add_text('0_0', 'Username (param)');
+	$status->add_unit('1_0', WebAppFramework::Unit::Variable->new('Variable' => 'params.Username'));
+	$status->add_text('0_1', 'Token');
+	$status->add_unit('1_1', WebAppFramework::Unit::Variable->new('Variable' => 'params.UserLoginToken'));
+	$status->add_text('0_2', 'formdata.Something')
+		->link_to('Main', 'Text' => 'params.Username', 'Value' => 'CONSTANT:2')	# link to another page
+		->set('class' => 'linkstyle');	# set another attributes in the link
+	$status->add_unit('1_2', WebAppFramework::Unit::Variable->new('Variable' => 'formdata.Something'));
+	$status->add_text('0_3', 'Username (login)');
+	$status->add_unit('1_3', WebAppFramework::Unit::OutputIf->new('Condition' => "WAF::Var('Security.IsAuthenticated')",
+									'@true' => WebAppFramework::Unit::Variable->new('Variable' => 'Security.Username'),
+									'@false' => WebAppFramework::Unit::TranslatedText->new('Text' => '(no user logged in)')));
+	$status->add_text('0_4', 'User is authenticated?');
+	$status->add_unit('1_4', WebAppFramework::Unit::Variable->new('Variable' => 'Security.IsAuthenticated'));
+	$status->add_text('0_5', 'User Credentials');
+	$status->add_unit('1_5', WebAppFramework::Unit::Variable->new('Variable' => 'Security.Credentials'));
+	
+	$page->add_unit('STATUS', $status);
+	
+	return $page
+}
+
+1;
+

Added: box/features/codeforintegration/test/webappframework/TestWebApp.schema
===================================================================
--- box/features/codeforintegration/test/webappframework/TestWebApp.schema	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/TestWebApp.schema	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,35 @@
+
+CREATE TABLE tItems (
+	fID `AUTO_INCREMENT_INT,
+	fName VARCHAR(64) NOT NULL,
+	fString VARCHAR(64) NOT NULL,
+	fInteger INT NOT NULL,
+	fColour INT NOT NULL,
+	fBoolean TINYINT NOT NULL,
+	fCreatedBy VARCHAR(64) NOT NULL
+);
+
+INSERT INTO tItems(fName,fString,fInteger,fColour,fBoolean,fCreatedBy) VALUES('One','Bath',23,2,0,'DEFAULT');
+INSERT INTO tItems(fName,fString,fInteger,fColour,fBoolean,fCreatedBy) VALUES('Two','Oxford',245,8,1,'DEFAULT');
+
+CREATE TABLE tUsers (
+	fID `AUTO_INCREMENT_INT,
+	fUsername VARCHAR(64) NOT NULL,
+	fPassword VARCHAR(64) NOT NULL
+);
+
+INSERT INTO tUsers(fUsername,fPassword) VALUES('user1', 'one');
+INSERT INTO tUsers(fUsername,fPassword) VALUES('second', 'two');
+
+CREATE TABLE tColours (
+	fColourID INT NOT NULL,
+	fColourName VARCHAR(64) NOT NULL
+);
+
+INSERT INTO tColours(fColourID,fColourName) VALUES(2,'Blue');
+INSERT INTO tColours(fColourID,fColourName) VALUES(3,'Green');
+INSERT INTO tColours(fColourID,fColourName) VALUES(5,'Orange');
+INSERT INTO tColours(fColourID,fColourName) VALUES(6,'Red');
+INSERT INTO tColours(fColourID,fColourName) VALUES(7,'Pink');
+INSERT INTO tColours(fColourID,fColourName) VALUES(8,'Violet');
+

Added: box/features/codeforintegration/test/webappframework/TestWebAppCode.cpp
===================================================================
--- box/features/codeforintegration/test/webappframework/TestWebAppCode.cpp	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/TestWebAppCode.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,63 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    TestWebAppCode.cpp
+//		Purpose: Test code for web app
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+// #include "autogen_webapp/TestWebAppPageLogin.h"
+#include "autogen_webapp/TestWebAppPageMain.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    TestWebAppFormLogin::Validate()
+//		Purpose: Extra validation for form
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+
+// MOVED TO Login.pl file with Code unit
+
+/* void TestWebAppFormLogin::Validate()
+{
+	// Valid password is the username backwards, generate it
+	std::string validpass;
+	for(std::string::reverse_iterator i(mUsername.rbegin()); i != mUsername.rend(); ++i)
+	{
+		validpass += *i;
+	}
+	
+	if(validpass == mPassword && validpass.size() > 0)
+	{
+		mPasswordValidityError = WebAppForm::Valid;
+	}
+}*/
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    TestWebAppFormWidgets::Validate()
+//		Purpose: Extra validation for form on Main page
+//		Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+void TestWebAppFormWidgets::Validate()
+{
+	// Add the error message from the form.
+	for(std::vector<int32_t>::const_iterator i(mEmitErrors.begin()); i != mEmitErrors.end(); ++i)
+	{
+		AddError(GetTranslatedStrings()[*i]);
+	}
+}
+
+
+

Added: box/features/codeforintegration/test/webappframework/makeCAPStranslation.pl
===================================================================
--- box/features/codeforintegration/test/webappframework/makeCAPStranslation.pl	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/makeCAPStranslation.pl	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+use strict;
+
+my $template_in = 'Templates/TestWebAppMain.en.html';
+my $template_out = 'Templates/TestWebAppMain.CAPS.html';
+my $language_file = 'Languages/CAPS.txt';
+
+if(0)
+{ ### no longer do the templates in a translation -- uses marker system instead
+# 'translate' the main HTML template
+open FL,$template_in or die "Can't open HTML template file";
+my $t;
+read FL,$t,-s $template_in;
+close FL;
+
+# turn into upper case
+$t =~ s/>(.*?)</'>'.uc($1).'<'/gse;
+# put back HTML entities to lowercase
+$t =~ s/&(\w+?);/'&'.lc($1).';'/ge;
+
+open OUT,'>'.$template_out or die "Can't open output HTML template file";
+print OUT $t;
+close OUT;
+}  ### no longer do the templates in a translation -- uses marker system instead
+
+# 'translate' the language file
+open FL,$language_file or die "Can't open language file";
+open OUT,'>'.$language_file.'new' or die "Can't open temporary output language file";
+while(<FL>)
+{
+	if(m/\A(#|@|>|========)/)
+	{
+		print OUT
+	}
+	else
+	{
+		my $x = $_;
+		my %protect;
+		my $pn = 0;
+		$x =~ s/\{(.+?)\}/$protect{$pn}=$1;'{'.$pn++.'}'/ge;
+		$x = uc($x);
+		$x =~ s/\{(.+?)\}/'{'.$protect{$1}.'}'/ge;
+		print OUT $x
+	}
+}
+close OUT;
+close FL;
+rename $language_file.'new',$language_file;


Property changes on: box/features/codeforintegration/test/webappframework/makeCAPStranslation.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: box/features/codeforintegration/test/webappframework/testfiles/testwebapp.conf
===================================================================
--- box/features/codeforintegration/test/webappframework/testfiles/testwebapp.conf	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/testfiles/testwebapp.conf	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,13 @@
+
+AddressPrefix = http://localhost:1080
+
+DatabaseDriver = sqlite
+DatabaseConnection = testfiles/database.sqlite
+
+CredentialTokenSecret = 38ab33982sjfsd9384
+
+Server
+{
+	PidFile = testfiles/testwebapp.pid
+	ListenAddresses = inet:localhost:1080
+}

Added: box/features/codeforintegration/test/webappframework/testwebappframework.cpp
===================================================================
--- box/features/codeforintegration/test/webappframework/testwebappframework.cpp	                        (rev 0)
+++ box/features/codeforintegration/test/webappframework/testwebappframework.cpp	2006-09-01 10:20:43 UTC (rev 926)
@@ -0,0 +1,189 @@
+// --------------------------------------------------------------------------
+//
+// File
+//		Name:    testwebappframework.cpp
+//		Purpose: Test simple web application framework
+//		Created: 30/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "Test.h"
+#include "autogen_webapp/TestWebAppServer.h"
+#include "autogen_db/TestWebApp_schema.h"
+#include "DatabaseConnection.h"
+#include "WAFUtilityFns.h"
+#include "WAFUKPostcode.h"
+
+#include "MemLeakFindOn.h"
+
+void test_ukpostcode_validate(const char *in, bool willValidate, const char *expected)
+{
+	WAFUKPostcode postcode;
+	bool result = postcode.ParseAndValidate(std::string(in));
+	std::string normalised(postcode.NormalisedPostcode());
+	TEST_THAT(result == willValidate);
+	TEST_THAT(normalised == expected);
+}
+
+void test_ukpostcode()
+{
+	test_ukpostcode_validate("a12BN", true, "A1 2BN");
+	test_ukpostcode_validate("a1 2BN", true, "A1 2BN");
+	test_ukpostcode_validate("   a12 BN   ", true, "A1 2BN");
+	test_ukpostcode_validate("ab2BN", false, "");
+	test_ukpostcode_validate("a132BN", true, "A13 2BN");
+	test_ukpostcode_validate("a132B3", false, "");
+	test_ukpostcode_validate("a1323A", false, "");
+	test_ukpostcode_validate("a13ABN", false, "");
+	test_ukpostcode_validate("aW32BN", true, "AW3 2BN");
+	test_ukpostcode_validate("a3q2BN", true, "A3Q 2BN");
+	test_ukpostcode_validate("aw872BN", true, "AW87 2BN");
+	test_ukpostcode_validate("aw8t 2BN", true, "AW8T 2BN");
+	test_ukpostcode_validate("awqt 2BN", false, "");
+	test_ukpostcode_validate("aw8722BN", false, "");
+}
+
+void test_phonenumber_validate(const char *in, bool willValidate, const char *expected)
+{
+	std::string canonical;
+	bool result = WAFUtility::ValidatePhoneNumber(std::string(in), canonical);
+	TEST_THAT(result == willValidate);
+	TEST_THAT(canonical == expected);
+}
+
+void test_phonenumber()
+{
+	test_phonenumber_validate("+44 12746382", true, "+44 12746382");
+	test_phonenumber_validate("44  12746382", true, "44 12746382");
+	test_phonenumber_validate("+44 12746382    ", true, "+44 12746382");
+	test_phonenumber_validate("02012746382", true, "02012746382");
+	test_phonenumber_validate("    02012746382", true, "02012746382");
+	test_phonenumber_validate("+44 127A46382", false, "");
+	test_phonenumber_validate("++44 12746382    ", false, "");
+	test_phonenumber_validate("44 12,746.382    ", true, "44 12,746.382");
+}
+
+void test_fixedpoint_parse(const char *in, int scale, bool parse, int expected)
+{
+	std::string input(in);
+	int result = 0;
+	bool parseOK = WAFUtility::ParseFixedPointDecimal(input, result, scale);
+	TEST_THAT(parseOK == parse);
+	if(parseOK)
+	{
+		TEST_THAT(result == expected);
+	}
+//	TRACE4("%s,%d,%d,%d\n", in, scale, parse, result);
+}
+
+void test_fixedpoint_gen(int in, int scale, int places, const char *expected)
+{
+	std::string out("rubbish");
+	WAFUtility::FixedPointDecimalToString(in, out, scale, places);
+	TEST_THAT(out == expected);
+//	TRACE4("%d,%d,%s,%s\n", in, scale, out.c_str(), expected);
+}
+
+void test_fixedpoint()
+{
+	// Test parsing strings
+	test_fixedpoint_parse("1.0103", 4, true, 10103);
+	test_fixedpoint_parse("1.010", 4, true, 10100);
+	test_fixedpoint_parse("1.00001", 4, true, 10000);
+	test_fixedpoint_parse("1.00006", 4, true, 10001);
+	test_fixedpoint_parse("pants", 4, false, 0);
+	test_fixedpoint_parse("1,0", 4, false, 0);
+	test_fixedpoint_parse("1,0apples", 4, false, 0);
+	test_fixedpoint_parse("1.00006 pants", 4, false, 0);
+	test_fixedpoint_parse("  1 . 010   ", 4, true, 10100);
+	test_fixedpoint_parse("  1 . 01 0   ", 4, false, 10100);
+	// negative numbers
+	test_fixedpoint_parse("-1.00001", 4, true, -10000);
+	test_fixedpoint_parse("-100.00", 4, true, -1000000);
+
+	// Check representation of negative numbers
+	{
+		int32_t n1,n2;
+		TEST_THAT(WAFUtility::ParseFixedPointDecimal(std::string("-23.56"), n1, 4));
+		TEST_THAT(WAFUtility::ParseFixedPointDecimal(std::string("-12.87"), n2, 4));
+		std::string out;
+		WAFUtility::FixedPointDecimalToString(n1+n2, out, 4, 4);
+		TEST_THAT(out == "-36.4300");
+	}
+
+	// Test generating strings
+	test_fixedpoint_gen(10003, 4, 2, "1.0003");
+	test_fixedpoint_gen(10020, 4, 2, "1.002");
+	test_fixedpoint_gen(10100, 4, 2, "1.01");
+	test_fixedpoint_gen(19000, 4, 2, "1.90");
+	test_fixedpoint_gen(-19000, 4, 2, "-1.90");
+	test_fixedpoint_gen(10000, 4, 2, "1.00");
+	test_fixedpoint_gen(10000, 4, 0, "1");
+	test_fixedpoint_gen(2360000, 4, 0, "236");
+	test_fixedpoint_gen(2360020, 4, 0, "236.002");
+	test_fixedpoint_gen(0, 4, 0, "0");
+	test_fixedpoint_gen(0, 4, 2, "0.00");
+
+	// Test all possible lengths to make sure the embedded table is correct
+	{
+		std::string expected("1");
+		int v = 1;
+		for(int l = 0; l <= WAFUTILITY_FIXEDPOINT_MAX_SCALE_DIGITS; ++l)
+		{
+			test_fixedpoint_gen(v, l, l, expected.c_str());
+			test_fixedpoint_parse(expected.c_str(), l, true, v);
+			// Next!
+			if(l == 0) expected += '.';
+			v *= 10;
+			v += (l+1);
+			expected += (l+1)+'0';
+		}
+	}
+}
+
+int test(int argc, const char *argv[])
+{
+	if(argc >= 2 && ::strcmp(argv[1], "server") == 0)
+	{
+		// Run a server
+		TestWebAppServer server;
+		return server.Main("doesnotexist", argc - 1, argv + 1);
+	}
+	
+	// Test some utility functions
+	test_fixedpoint();
+	test_phonenumber();
+	test_ukpostcode();
+	
+	// Create the database
+	::unlink("testfiles/database.sqlite");
+	{
+		DatabaseConnection db;
+		db.Connect(std::string("sqlite"), std::string("testfiles/database.sqlite"), 1000);
+		TestWebApp_Create(db);
+	}
+	
+	// Start the server
+	int pid = LaunchServer("./test server testfiles/testwebapp.conf", "testfiles/testwebapp.pid");
+	TEST_THAT(pid != -1 && pid != 0);
+	if(pid > 0)
+	{
+		// Run the request script
+//		TEST_THAT(::system("perl testfiles/testrequests.pl") == 0);
+	
+		// Kill it
+		TEST_THAT(KillServer(pid));
+		TestRemoteProcessMemLeaks("testwebapp.memleaks");
+	}
+	
+	printf("\nTo launch the server, type:\n    ./test server testfiles/testwebapp.conf\n\nTo try the server with a web browser, go to\n    http://localhost:1080/test/en/lgin/username/0\n    http://localhost:1080/test/CAPS/lgin/username/0\n(different translations)\n\nTo stop the server, type\n    xargs kill < testfiles/testwebapp.pid\n\n");
+
+	return 0;
+}
+


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 11:20:47 2006
From: boxbackup-dev@fluffy.co.uk (boxbackup-dev@fluffy.co.uk)
Date: Fri, 01 Sep 2006 10:20:47 -0000
Subject: [Box Backup-dev] =?utf-8?q?Re=3A_=236=3A_Contribute_code=3A_SMTP_client=2C_HTTP_server=2C_?=
 =?utf-8?q?Database_drivers=2C_Web_app_framework?=
In-Reply-To: <072.ab75057719ec9ae573455ab70d09b933@fluffy.co.uk>
References: <072.ab75057719ec9ae573455ab70d09b933@fluffy.co.uk>
Message-ID: <081.d5a311454e65030c4a13e6c6b18f3f95@fluffy.co.uk>

--===============0964352776==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64



--===============0964352776==--

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 12:57:49 2006
From: boxbackup-dev@fluffy.co.uk (James O'Gorman)
Date: Fri, 1 Sep 2006 12:57:49 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk> <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk>
Message-ID: <20060901115748.GI81028@netinertia.co.uk>

On Fri, Sep 01, 2006 at 09:02:12AM +0100, Ben Summers wrote:
> On 31 Aug 2006, at 23:52, Martin Ebourne wrote:
> 
> >Given the amount of autogenerated traffic now hitting the -dev list I
> >wonder if it's time for a -commit list for all trac/svn traffic? That
> >way interested people could monitor the -dev list without being  
> >spammed.
> >Presumably the main developers would keep both though.
> 
> Good plan. I'll get this set up as soon as I can.

Give me the nod when it's done and I'll change the commit scripts
appropriately.

I take it ticket emails will still go to -dev and not -commit?

James

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 14:12:04 2006
From: boxbackup-dev@fluffy.co.uk (Martin Ebourne)
Date: Fri, 01 Sep 2006 14:12:04 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <20060901115748.GI81028@netinertia.co.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk>
 <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk>
 <20060901115748.GI81028@netinertia.co.uk>
Message-ID: <1157116324.30210.5.camel@avenin.ebourne.me.uk>

On Fri, 2006-09-01 at 12:57 +0100, James O'Gorman wrote:
> On Fri, Sep 01, 2006 at 09:02:12AM +0100, Ben Summers wrote:
> > On 31 Aug 2006, at 23:52, Martin Ebourne wrote:
> > 
> > >Given the amount of autogenerated traffic now hitting the -dev list I
> > >wonder if it's time for a -commit list for all trac/svn traffic? That
> > >way interested people could monitor the -dev list without being  
> > >spammed.
> > >Presumably the main developers would keep both though.
> > 
> > Good plan. I'll get this set up as soon as I can.
> 
> Give me the nod when it's done and I'll change the commit scripts
> appropriately.
> 
> I take it ticket emails will still go to -dev and not -commit?

I was thinking that all automatic traffic including trac output would go
to -commit. There's nearly as much ticket traffic as commit traffic and
I wouldn't want to encourage people to use trac as a discussion forum
(as so often happens with bugzilla). Maybe give -commit a better name if
you can think of one.

Let's see what Ben thinks.

Cheers,

Martin.


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 15:32:15 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 15:32:15 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <1157116324.30210.5.camel@avenin.ebourne.me.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk> <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk> <20060901115748.GI81028@netinertia.co.uk> <1157116324.30210.5.camel@avenin.ebourne.me.uk>
Message-ID: <068267A2-5C2D-4271-9FF7-BB085D753E85@fluffy.co.uk>

On 1 Sep 2006, at 14:12, Martin Ebourne wrote:

> On Fri, 2006-09-01 at 12:57 +0100, James O'Gorman wrote:
>> On Fri, Sep 01, 2006 at 09:02:12AM +0100, Ben Summers wrote:
>>> On 31 Aug 2006, at 23:52, Martin Ebourne wrote:
>>>
>>>> Given the amount of autogenerated traffic now hitting the -dev  
>>>> list I
>>>> wonder if it's time for a -commit list for all trac/svn traffic?  
>>>> That
>>>> way interested people could monitor the -dev list without being
>>>> spammed.
>>>> Presumably the main developers would keep both though.
>>>
>>> Good plan. I'll get this set up as soon as I can.
>>
>> Give me the nod when it's done and I'll change the commit scripts
>> appropriately.
>>
>> I take it ticket emails will still go to -dev and not -commit?
>
> I was thinking that all automatic traffic including trac output  
> would go
> to -commit. There's nearly as much ticket traffic as commit traffic  
> and
> I wouldn't want to encourage people to use trac as a discussion forum
> (as so often happens with bugzilla). Maybe give -commit a better  
> name if
> you can think of one.
>
> Let's see what Ben thinks.

    http://lists.warhead.org.uk/mailman/listinfo/boxbackup-commit

I've copied the -dev membership to -commit, but if you want a digest  
you'll have to set that up yourself.

I agree that both commits and trac tickets should go there.

Ben




From boxbackup-dev@fluffy.co.uk  Fri Sep  1 15:51:29 2006
From: boxbackup-dev@fluffy.co.uk (Martin Ebourne)
Date: Fri, 01 Sep 2006 15:51:29 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <068267A2-5C2D-4271-9FF7-BB085D753E85@fluffy.co.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk>
 <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk>
 <20060901115748.GI81028@netinertia.co.uk>
 <1157116324.30210.5.camel@avenin.ebourne.me.uk>
 <068267A2-5C2D-4271-9FF7-BB085D753E85@fluffy.co.uk>
Message-ID: <1157122289.30210.19.camel@avenin.ebourne.me.uk>

On Fri, 2006-09-01 at 15:32 +0100, Ben Summers wrote:
>     http://lists.warhead.org.uk/mailman/listinfo/boxbackup-commit
> 
> I've copied the -dev membership to -commit, but if you want a digest  
> you'll have to set that up yourself.

I don't know if you already have, or even if its possible, but setting
the reply-to for the commit list to -dev would probably be a good idea.
Also restricting posts to it except for trac/svn user.

Cheers,

Martin.


From boxbackup-dev@fluffy.co.uk  Fri Sep  1 15:56:45 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Fri, 1 Sep 2006 15:56:45 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <1157122289.30210.19.camel@avenin.ebourne.me.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk> <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk> <20060901115748.GI81028@netinertia.co.uk> <1157116324.30210.5.camel@avenin.ebourne.me.uk> <068267A2-5C2D-4271-9FF7-BB085D753E85@fluffy.co.uk> <1157122289.30210.19.camel@avenin.ebourne.me.uk>
Message-ID: <435373AD-5951-4CFF-9AD6-87FE24A05BA5@fluffy.co.uk>

On 1 Sep 2006, at 15:51, Martin Ebourne wrote:

> On Fri, 2006-09-01 at 15:32 +0100, Ben Summers wrote:
>>     http://lists.warhead.org.uk/mailman/listinfo/boxbackup-commit
>>
>> I've copied the -dev membership to -commit, but if you want a digest
>> you'll have to set that up yourself.
>
> I don't know if you already have, or even if its possible, but setting
> the reply-to for the commit list to -dev would probably be a good  
> idea.
> Also restricting posts to it except for trac/svn user.


I've done that now.

Ben



From boxbackup-dev@fluffy.co.uk  Fri Sep  1 16:03:19 2006
From: boxbackup-dev@fluffy.co.uk (James O'Gorman)
Date: Fri, 1 Sep 2006 16:03:19 +0100
Subject: [Box Backup-dev] commit list anyone?
In-Reply-To: <068267A2-5C2D-4271-9FF7-BB085D753E85@fluffy.co.uk>
References: <1157064778.26754.12.camel@avenin.ebourne.me.uk> <C8361095-BA5E-4E1F-8CEE-23B1F3563701@fluffy.co.uk> <20060901115748.GI81028@netinertia.co.uk> <1157116324.30210.5.camel@avenin.ebourne.me.uk> <068267A2-5C2D-4271-9FF7-BB085D753E85@fluffy.co.uk>
Message-ID: <20060901150319.GJ81028@netinertia.co.uk>

On Fri, Sep 01, 2006 at 03:32:15PM +0100, Ben Summers wrote:
>    http://lists.warhead.org.uk/mailman/listinfo/boxbackup-commit
> 
> I've copied the -dev membership to -commit, but if you want a digest  
> you'll have to set that up yourself.
> 
> I agree that both commits and trac tickets should go there.

Commit messages have been updated. The Trac emails already had a
reply-to setting of -dev so I've left it at that, but posts to -commit.

James

From boxbackup-dev@fluffy.co.uk  Fri Sep  1 18:31:15 2006
From: boxbackup-dev@fluffy.co.uk (Martin Ebourne)
Date: Fri, 01 Sep 2006 18:31:15 +0100
Subject: [Box Backup-dev] Away
Message-ID: <1157131875.30210.25.camel@avenin.ebourne.me.uk>

I'm away for a week or so. I'll review any of Chris's changes when I get
time after I get back, unless anyone else has already beaten me to
it. :)

Seeya,

Martin.


From boxbackup-dev@fluffy.co.uk  Sat Sep  9 12:03:58 2006
From: boxbackup-dev@fluffy.co.uk (Andrei)
Date: Sat, 9 Sep 2006 14:03:58 +0300
Subject: [Box Backup-dev] Hello all - win32 client
In-Reply-To: <MDAEMON-F200607211649.AA4908703md50000001226@bitshk.com>
References: <MDAEMON-F200607211649.AA4908703md50000001226@bitshk.com>
Message-ID: <bab9f1d50609090403h5ca4a27cj2b6ad5c8c041ddff@mail.gmail.com>

Hello,

I'm somewhat confused by your statement...

>From the FAQ:

    "4. Can I use Express Editions for commercial use?

    Yes, there are no licensing restrictions for applications built
using the Express Editions."

http://blogs.msdn.com/alexbarn/archive/2006/04/19/579529.aspx

Regards,
Andrei

On 7/21/06, Stuart Sanders <stuart@bitshk.com> wrote:
> The "free" version of VS 2005 (ie VS Express) has a non-distributable license.
>
> So to legally distribute anything he compiles he would need to pay for Visual Studio.
>
> Stuart
>
> > -----Original Message-----
> > From: boxbackup-dev-admin@fluffy.co.uk
> > [mailto:boxbackup-dev-admin@fluffy.co.uk] On Behalf Of Nick Knight
> > Sent: Friday, 21 July 2006 4:40 PM
> > To: boxbackup-dev@fluffy.co.uk
> > Subject: RE: [Box Backup-dev] Hello all - win32 client
> >
> >
> > Hello Chris,
> >
> > This is probably leads to the need for support two platforms!
> > You can't
> > remove personal opinion! Not sure why you cannot distribute binaries
> > produced by MSVC?
> >
> > In this day and age 2GB files are not uncommon, so it needs doing - I
> > will have a look and assess it.
> >
> > Still trying to compile though and it barfs on no
> > BoxVersion.h, where is
> > this supposed to be or got from.
> >
> > Thanks
> >
> > Nick
> >
> > -----Original Message-----
> > From: boxbackup-dev-admin@fluffy.co.uk
> > [mailto:boxbackup-dev-admin@fluffy.co.uk] On Behalf Of Chris Wilson
> > Sent: 21 July 2006 09:32
> > To: boxbackup-dev@fluffy.co.uk
> > Subject: RE: [Box Backup-dev] Hello all - win32 client
> >
> > Hi Nick,
> >
> > > Sorry Chris, I am with Gary, the Microsoft devel environment (I use
> > > 2003) its toolset is pretty good for debugging, I can't
> > comment on the
> > > inteli-sense in 2005 but I work on 2003.
> >
> > Sorry Nick, I am with GCC, because I can distribute the resulting
> > binaries. If I developed with Visual Studio and then distributed
> > untested
> > MinGW binaries, what would happen? :-)
> >
> > > Also in my humble view, Microsoft wrote the compiler, libraries and
> > OS,
> > > so will they not work better together?
> >
> > But they did not write Box Backup, so perhaps they will not work well
> > together? ;-)
> >
> > Box has always been developed (on other platforms) using gcc.
> >
> > > In fact I would go as far to say that in my days as an embedded
> > software
> > > engineer, we always wrote for Microsoft ide, then port to
> > the platform
> > > which would have much faster development cycles!
> >
> > > 1. Large file support - verify it is all working
> >
> > It definitely is not. The POSIX APIs used by Box on Windows do not
> > support
> > files over 2 GB, because they don't have a 64-bit file offset.
> >
> > The problem, unfortunately, is to replace ALL the POSIX APIs with
> > emulated
> > replacements using the Windows APi. That will not be easy.
> >
> > > 3. Look at the possibility of Shadow Copy, it looks quite
> > tricky so no
> > > promises! But this looks much needed for the likes of backing up
> > > Exchange
> >
> > This will be the first major problem for MinGW - the VSS API is cl
> >
> > > The problem without shadow copy and trying to backup exchange (or
> > > similar - point 3 shadow copy) is you have to back an intermediary
> > file
> > > using ntbackup - or just pulling out the database with exchange
> > > shutdown, but the problem with this is the id of the file
> > changes so I
> > > believe box treats it as a new file so doesn't even try to diff??
> >
> > Box will try to diff if a file is replaced by another with the same
> > name,
> > as well as the contents of a file changing. As far as I know,
> > the ID is
> > only used to track renamed files.
> >
> > Cheers, Chris.
> > --
> > _ ___ __     _
> >   / __/ / ,__(_)_  | Chris Wilson <0000 at qwirx.com> - Cambs UK |
> > / (_/ ,\/ _/ /_ \ | Security/C/C++/Java/Perl/SQL/HTML Developer |
> > \ _/_/_/_//_/___/ | We are GNU-free your mind-and your software |
> >
> > _______________________________________________
> > Boxbackup-dev mailing list
> > Boxbackup-dev@fluffy.co.uk
> > http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev
> > _______________________________________________
> > Boxbackup-dev mailing list
> > Boxbackup-dev@fluffy.co.uk
> > http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev
> >
>
> _______________________________________________
> Boxbackup-dev mailing list
> Boxbackup-dev@fluffy.co.uk
> http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev
>

From boxbackup-dev@fluffy.co.uk  Sat Sep  9 13:53:07 2006
From: boxbackup-dev@fluffy.co.uk (Stuart Sanders)
Date: Sat, 09 Sep 2006 20:53:07 +0800
Subject: [Box Backup-dev] Hello all - win32 client
In-Reply-To: <bab9f1d50609090403h5ca4a27cj2b6ad5c8c041ddff@mail.gmail.com>
References: <MDAEMON-F200607211649.AA4908703md50000001226@bitshk.com> <bab9f1d50609090403h5ca4a27cj2b6ad5c8c041ddff@mail.gmail.com>
Message-ID: <WorldClient-F200609092053.AA53070014@bitshk.com>

I stand corrected.

The news I had read when I first heard that VSE was going to be released
stated that it would be restricted.  

The FAQ clearly states that this is not the case anymore, and it makes
sense from a marketing point of view to not have restrictions to limit
people using free open source tools.

Stuart

-----Original Message-----
From: Andrei <andrei@soft-dev.com>
To: boxbackup-dev@fluffy.co.uk
Date: Sat, 9 Sep 2006 14:03:58 +0300
Subject: Re: [Box Backup-dev] Hello all - win32 client

> Hello,
> 
> I'm somewhat confused by your statement...
> 
> From the FAQ:
> 
>     "4. Can I use Express Editions for commercial use?
> 
>     Yes, there are no licensing restrictions for applications built
> using the Express Editions."
> 
> http://blogs.msdn.com/alexbarn/archive/2006/04/19/579529.aspx
> 
> Regards,
> Andrei
> 
> On 7/21/06, Stuart Sanders <stuart@bitshk.com> wrote:
> > The "free" version of VS 2005 (ie VS Express) has a non-distributable
> license.
> >
> > So to legally distribute anything he compiles he would need to pay
> for Visual Studio.
> >
> > Stuart
> >
> > > -----Original Message-----
> > > From: boxbackup-dev-admin@fluffy.co.uk
> > > [mailto:boxbackup-dev-admin@fluffy.co.uk] On Behalf Of Nick Knight
> > > Sent: Friday, 21 July 2006 4:40 PM
> > > To: boxbackup-dev@fluffy.co.uk
> > > Subject: RE: [Box Backup-dev] Hello all - win32 client
> > >
> > >
> > > Hello Chris,
> > >
> > > This is probably leads to the need for support two platforms!
> > > You can't
> > > remove personal opinion! Not sure why you cannot distribute
> binaries
> > > produced by MSVC?
> > >
> > > In this day and age 2GB files are not uncommon, so it needs doing -
> I
> > > will have a look and assess it.
> > >
> > > Still trying to compile though and it barfs on no
> > > BoxVersion.h, where is
> > > this supposed to be or got from.
> > >
> > > Thanks
> > >
> > > Nick
> > >
> > > -----Original Message-----
> > > From: boxbackup-dev-admin@fluffy.co.uk
> > > [mailto:boxbackup-dev-admin@fluffy.co.uk] On Behalf Of Chris Wilson
> > > Sent: 21 July 2006 09:32
> > > To: boxbackup-dev@fluffy.co.uk
> > > Subject: RE: [Box Backup-dev] Hello all - win32 client
> > >
> > > Hi Nick,
> > >
> > > > Sorry Chris, I am with Gary, the Microsoft devel environment (I
> use
> > > > 2003) its toolset is pretty good for debugging, I can't
> > > comment on the
> > > > inteli-sense in 2005 but I work on 2003.
> > >
> > > Sorry Nick, I am with GCC, because I can distribute the resulting
> > > binaries. If I developed with Visual Studio and then distributed
> > > untested
> > > MinGW binaries, what would happen? :-)
> > >
> > > > Also in my humble view, Microsoft wrote the compiler, libraries
> and
> > > OS,
> > > > so will they not work better together?
> > >
> > > But they did not write Box Backup, so perhaps they will not work
> well
> > > together? ;-)
> > >
> > > Box has always been developed (on other platforms) using gcc.
> > >
> > > > In fact I would go as far to say that in my days as an embedded
> > > software
> > > > engineer, we always wrote for Microsoft ide, then port to
> > > the platform
> > > > which would have much faster development cycles!
> > >
> > > > 1. Large file support - verify it is all working
> > >
> > > It definitely is not. The POSIX APIs used by Box on Windows do not
> > > support
> > > files over 2 GB, because they don't have a 64-bit file offset.
> > >
> > > The problem, unfortunately, is to replace ALL the POSIX APIs with
> > > emulated
> > > replacements using the Windows APi. That will not be easy.
> > >
> > > > 3. Look at the possibility of Shadow Copy, it looks quite
> > > tricky so no
> > > > promises! But this looks much needed for the likes of backing up
> > > > Exchange
> > >
> > > This will be the first major problem for MinGW - the VSS API is cl
> > >
> > > > The problem without shadow copy and trying to backup exchange (or
> > > > similar - point 3 shadow copy) is you have to back an
> intermediary
> > > file
> > > > using ntbackup - or just pulling out the database with exchange
> > > > shutdown, but the problem with this is the id of the file
> > > changes so I
> > > > believe box treats it as a new file so doesn't even try to diff??
> > >
> > > Box will try to diff if a file is replaced by another with the same
> > > name,
> > > as well as the contents of a file changing. As far as I know,
> > > the ID is
> > > only used to track renamed files.
> > >
> > > Cheers, Chris.
> > > --
> > > _ ___ __     _
> > >   / __/ / ,__(_)_  | Chris Wilson <0000 at qwirx.com> - Cambs UK |
> > > / (_/ ,\/ _/ /_ \ | Security/C/C++/Java/Perl/SQL/HTML Developer |
> > > \ _/_/_/_//_/___/ | We are GNU-free your mind-and your software |
> > >
> > > _______________________________________________
> > > Boxbackup-dev mailing list
> > > Boxbackup-dev@fluffy.co.uk
> > > http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev
> > > _______________________________________________
> > > Boxbackup-dev mailing list
> > > Boxbackup-dev@fluffy.co.uk
> > > http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev
> > >
> >
> > _______________________________________________
> > Boxbackup-dev mailing list
> > Boxbackup-dev@fluffy.co.uk
> > http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev
> >
> _______________________________________________
> Boxbackup-dev mailing list
> Boxbackup-dev@fluffy.co.uk
> http://lists.warhead.org.uk/mailman/listinfo/boxbackup-dev



From boxbackup-dev@fluffy.co.uk  Wed Sep 13 16:44:53 2006
From: boxbackup-dev@fluffy.co.uk (Ben Summers)
Date: Wed, 13 Sep 2006 16:44:53 +0100
Subject: [Box Backup-dev] OT: Looking for a PHP programmer
Message-ID: <5AB40D22-0740-4EF8-9268-31029CC7021B@fluffy.co.uk>

Totally off topic, I know. But... I need a PHP programmer for a few  
days work customising osCommerce. Anyone? Anyone know anyone good?

Thanks,

Ben





From boxbackup-dev@fluffy.co.uk  Wed Sep 13 16:44:06 2006
From: boxbackup-dev@fluffy.co.uk (James O'Gorman)
Date: Wed, 13 Sep 2006 16:44:06 +0100
Subject: [Box Backup-dev] svn/trac server downtime this afternoon
Message-ID: <45082746.4050502@netinertia.co.uk>

Hi all,

Just a quick heads-up; bbdev.fluffy.co.uk will be down for 5-10 mins
later on this afternoon (probably just after 5pm GMT) while I apply some
security patches. I'll post back here when it's up again.

James

From boxbackup-dev@fluffy.co.uk  Wed Sep 13 17:26:26 2006
From: boxbackup-dev@fluffy.co.uk (James O'Gorman)
Date: Wed, 13 Sep 2006 17:26:26 +0100
Subject: [Box Backup-dev] svn/trac server downtime this afternoon
In-Reply-To: <45082746.4050502@netinertia.co.uk>
References: <45082746.4050502@netinertia.co.uk>
Message-ID: <45083132.4080602@netinertia.co.uk>

James O'Gorman wrote:
> Hi all,
> 
> Just a quick heads-up; bbdev.fluffy.co.uk will be down for 5-10 mins
> later on this afternoon (probably just after 5pm GMT) while I apply some
> security patches. I'll post back here when it's up again.

Took a little longer than expected (hurrah for serial consoles at 9600
baud...) but it's back up now.

James

From boxbackup-dev@fluffy.co.uk  Wed Sep 27 18:29:53 2006
From: boxbackup-dev@fluffy.co.uk (Chris Wilson)
Date: Wed, 27 Sep 2006 18:29:53 +0100 (BST)
Subject: [Box Backup-dev] OT: How To Accelerate Your Internet (reviewers needed)
Message-ID: <Pine.LNX.4.64.0609271828250.16043@localhost>

Hi all,

(Ben, or whoever moderates the list, please delete this message from the 
moderation queue when you get around to it, thanks! I sent it from another 
address on Monday and it didn't get to the list).

As part of my work, I've been part of a team that is writing a Creative 
Commons book on bandwidth management. We hope that this will be of great 
use to network administrators, especially in developing countries. The 
book is close to completion and we are looking for technical reviewers 
before publication.

There is a lot of content on open source systems such as Linux, BSDs, 
Apache, Squid, Exim and Postfix, and I'm sure that many of you are very 
experienced in such things. I hope that some of you will consider 
reviewing and improving our work and this book. Please see the attached 
message, below, for details.

Thanks in advance.

Cheers, Chris.
-- 
(aidworld) chris wilson | chief engineer (http://www.aidworld.org)

-------- Forwarded Message --------
From: Rob Flickenger <rob@hackerfriendly.com>
Subject: [Bmo-team] How To Accelerate Your Internet rough draft
Date: Sun, 24 Sep 2006 21:49:59 -0700

Hello, everyone--

I'm very pleased to announced that we have completed the first rough
draft of the book!

http://hackerfriendly.com/pub/xlr8-060924.pdf

This version is not for general distribution, as there is still a bit
of work to do:

* The final copy edit pass is underway.  This will fix any remaining
spelling and grammatical problems, as well as standardize, er,
standardise on international spellings.

* I still have not received a bio from everyone.  I filled in some
very bare entries for a few of you, and may have forgotten a few
(sorry, Duncan!)  Please put your bio up on the wiki or send it to me
if you haven't already.  At their request, the Aidworld folks have a
single (slightly longer) entry for all nine of them.

* We're still waiting for the line drawings.  These should be ready
some time this week, and are represented by the green "figure:" lines.

* Since the figures are not yet complete, there are a whole bunch of
FIXME's referring to page numbers.  These are added at the very end,
since edits and figures will change the page count.

* You may also notice some unnatural line breaks.  Again, these will
be fixed once the figures and final edits are made.

* The Resources, Glossary, Index, and Squid ACL primer are still to
be compiled.

* Technical review.  I already have a few volunteers for technical
review, but if you have any colleagues who have the time to review a
chapter (or more) in the next week, please put them in touch with me
directly.  We have budget for this, and the golden rule is that you
cannot do tech review for your own work.  =)

That's all that comes to mind at the moment.  Please send me your
comments directly or post them to the list.  And congratulations!  We
should be going to print very soon now.

Cheers,

--Rob


!DSPAM:451ab0a3140838722917661!

From boxbackup-dev@fluffy.co.uk  Thu Sep 28 00:08:06 2006
From: boxbackup-dev@fluffy.co.uk (Martin Ebourne)
Date: Thu, 28 Sep 2006 00:08:06 +0100
Subject: [Box Backup-dev] OT: How To Accelerate Your Internet
 (reviewers needed)
In-Reply-To: <Pine.LNX.4.64.0609271828250.16043@localhost>
References: <Pine.LNX.4.64.0609271828250.16043@localhost>
Message-ID: <1159398487.20919.22.camel@avenin.ebourne.me.uk>

On Wed, 2006-09-27 at 18:29 +0100, Chris Wilson wrote:
> There is a lot of content on open source systems such as Linux, BSDs, 
> Apache, Squid, Exim and Postfix, and I'm sure that many of you are very 
> experienced in such things. I hope that some of you will consider 
> reviewing and improving our work and this book. Please see the attached 
> message, below, for details.

I have to ask: was the backup system that malfunctioned one day and used
up a lot of bandwidth Box Backup?

Cheers,

Martin.


From boxbackup-dev@fluffy.co.uk  Thu Sep 28 00:25:35 2006
From: boxbackup-dev@fluffy.co.uk (Chris Wilson)
Date: Thu, 28 Sep 2006 00:25:35 +0100 (BST)
Subject: [Box Backup-dev] OT: How To Accelerate Your Internet	(reviewers
 needed)
In-Reply-To: <1159398487.20919.22.camel@avenin.ebourne.me.uk>
References: <Pine.LNX.4.64.0609271828250.16043@localhost>
 <1159398487.20919.22.camel@avenin.ebourne.me.uk>
Message-ID: <Pine.LNX.4.62.0609280024240.27576@top.qwarx.com>

Hi Martin,

On Thu, 28 Sep 2006, Martin Ebourne wrote:

> I have to ask: was the backup system that malfunctioned one day and used 
> up a lot of bandwidth Box Backup?

No, rdiff-backup :-) It was a configuration error. Some big files were 
created that resulted in an additional 300 MB to back up that day, and 
rdiff-backup is quite inefficient in bandwidth, so it used a lot more than 
that.

Cheers. Chris.
-- 
_ ___ __     _
  / __/ / ,__(_)_  | Chris Wilson <0000 at qwirx.com> - Cambs UK |
/ (_/ ,\/ _/ /_ \ | Security/C/C++/Java/Perl/SQL/HTML Developer |
\ _/_/_/_//_/___/ | We are GNU-free your mind-and your software |


