#include "pch.hpp"
//===========================================================================//
// File:	 Raid.cpp														 //
// Project:  GameOS					                                         //
// Contents: code to submit raid bugs					  					 //
//---------------------------------------------------------------------------//
// Date			Who			Modification                                     // 
// 05/18/2000   bcrocco     Created											 //
//																			 //
//---------------------------------------------------------------------------//
// Copyright (C) 2000 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "ErrorHandler.hpp"
#include "Raid.hpp"
#include "registry.hpp"

extern char* ConvertToCRLF( char* String );
extern char* DecNumber( DWORD Value, bool DisplayMeg=1 );

extern HGOSHEAP DefaultHeap;
extern int HasAMD;
extern int HasCYRIX;
extern int HasINTEL;

//
//
//RAIDBUG
//Default Raid fields. Should be populated and passed to CRaid::SetDefRaidFields
//
RAIDBUG::RAIDBUG() : 
	pszTitle(NULL),
	pszAssignedTo("Active"),  
	pszEnvironment(NULL),
	pszOpenedBy(NULL),
	pszOpenedRev(NULL),
	pszSourceID(NULL),
	pszLang(NULL),
	nStatus(ACTIVE),
	nIssueType(OTHERISSUE),
	nAccessibility(NO),
	nSource(OTHERSOURCE),
	nHowFound(ADHOCGEN),
	nSeverity(3),
	nPriority(3)
{
}

//
//
//CODBCSQL
//Connects/Disconnects to SQLServer; Handles ODBC errors
//
CODBCSQL::CODBCSQL() : m_henv(SQL_NULL_HENV), 
					   m_hdbc(SQL_NULL_HDBC)
{
	szErrorMsg[0]='\0';
}

//
//
//CODBCSQL
//Checks to make sure Close() was called properly
//
CODBCSQL::~CODBCSQL() 
{
	gosASSERT(m_hdbc == SQL_NULL_HDBC);
	gosASSERT(m_henv == SQL_NULL_HENV);
}

//
//CODBCSQL::Open
//Connects to a SQLServer database via ODBC
//
bool CODBCSQL::Open(char* pszServer,char* pszUID,char* pszPWD,char* pszDatabase)
{
	//Check previous connection was closed
	gosASSERT(m_hdbc == SQL_NULL_HDBC);				

	//Validate the params
	gosASSERT(pszServer != NULL);						
	gosASSERT(pszUID != NULL);							
	gosASSERT(pszPWD != NULL);							
	gosASSERT(pszDatabase != NULL);					

    //Initialize ODBC and allocate the ODBC Environment handle
    retcode = _SQLAllocHandle(SQL_HANDLE_ENV, NULL, &m_henv);
	
	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_ENV,m_henv,szErrorMsg);
		Close();
		return false;
	}

    //Let ODBC know this application will use ODBC 3.0 format function calls
    retcode = _SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC3,SQL_IS_INTEGER);

	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_ENV,m_henv,szErrorMsg);
		Close();
		return false;
	}

    //Allocate an ODBC connection handle
    retcode = _SQLAllocHandle(SQL_HANDLE_DBC,m_henv, &m_hdbc);

	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_ENV,m_henv,szErrorMsg);
		Close();
		return false;
	}

	//Assemble the connection string
	SQLCHAR        ConnStrOut[MAXBUFLEN];
	SQLSMALLINT    cbConnStrOut = 0;
	SQLCHAR        ConnStrIn[MAXBUFLEN];

	sprintf((char*)ConnStrIn,"DRIVER={SQL Server};SERVER=%s;UID=%s;PWD=%s;DATABASE=%s;APP=RAID4",pszServer,pszUID,pszPWD,pszDatabase);

	//Make connection without data source. Ask that driver not prompt if insufficient information. 
	retcode = _SQLDriverConnect(m_hdbc,NULL,ConnStrIn,SQL_NTS,ConnStrOut,MAXBUFLEN,&cbConnStrOut,SQL_DRIVER_NOPROMPT);

	if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
	{
		errHandler(SQL_HANDLE_DBC,m_hdbc,szErrorMsg);
		Close();
		return false;
	}

	return true;
}

//
//CODBCSQL::Close
//Disconnects and cleans up the handles used by ODBC
//
bool CODBCSQL::Close()
{
	
	if(m_hdbc != SQL_NULL_HDBC)
	{
		//Disconnect from SQLServer
		retcode = _SQLDisconnect(m_hdbc);

		if (retcode != SQL_SUCCESS) 
		{
			errHandler(SQL_HANDLE_DBC,m_hdbc,szErrorMsg);
			return false;
		}

		//Free up connection resources 
		_SQLFreeHandle(SQL_HANDLE_DBC,m_hdbc);
	
		if (retcode != SQL_SUCCESS) 
		{
			errHandler(SQL_HANDLE_DBC,m_hdbc,szErrorMsg);
			return false;
		}

		m_hdbc = SQL_NULL_HDBC;
	}

	//Free up environment resources
	if(m_henv != SQL_NULL_HENV)
	{
		_SQLFreeHandle(SQL_HANDLE_ENV,m_henv);

		if (retcode != SQL_SUCCESS) 
		{
			errHandler(SQL_HANDLE_ENV,m_henv,szErrorMsg);
			return false;
		}	

		m_henv = SQL_NULL_HENV;
	}

	return true;
}

//
//CODBCSQL::errHandler
//Reports the details of errors returned by ODBC 
//NOTE: modified from MSDN. Search "How to process ODBC errors (ODBC)" 

void CODBCSQL::errHandler(SQLSMALLINT handleType,SQLHANDLE handle,char* pszErrorOut)
{						
	//Validate Params
	gosASSERT(pszErrorOut != NULL);

	//clear string
	*pszErrorOut = '\0';

	//For SQLGetDiagRec Calls
	SQLSMALLINT  cRecNmbr = 1;					//status record from which the application seeks information. numbered from 1
	UCHAR        uszSqlState[SQLSTATELEN] = "";	//buffer for the SQLSTATE error code (data source errors mapped by the ODBC driver)
	SQLINTEGER	 lNativeError = NULL;			//the native error code (errors from SQL Server, not the ODBC driver)
	UCHAR		 uszErrorMsg[MAXBUFLEN] = "";	//buffer for the actual error message text
	SQLSMALLINT  cbErrorMsg = 0;				//the number of characters available to return in the error message

	retcode = _SQLGetDiagRec(handleType, handle, cRecNmbr, uszSqlState, &lNativeError, uszErrorMsg, MAXBUFLEN - 1, &cbErrorMsg);

	//assemble error message 
	//BUGBUG: only grabs last error in set. (wasteful)
	char* curChar = (char*)uszErrorMsg;
	int nWritten  = 0;

	//eliminate the gross ODBC error messages in [ ]'s
	while(*curChar != '\0')
	{
		if(*curChar == '[')
		{
			while(*(curChar++) != ']');
		}
		else
			pszErrorOut[nWritten++] = (char)*curChar++;
	}

	pszErrorOut[nWritten++] = '\0';
}

//
//
//CODBCSQL::getErrorString()
//returns the last error message set by the raid class
//
char* CODBCSQL::getErrorString()
{
	if(szErrorMsg[0] == '\0')
		return NULL;
	else
		return szErrorMsg;
}

//
//
//CODBCSQL::setErrorString
//allows the developer to set the error string
//
void CODBCSQL::setErrorString(char* pszErrStr)
{
	strncpy(szErrorMsg,pszErrStr,MAXBUFLEN);
}


//
//
//CRaid
//Handles Raid specific tasks such as populating default bug fields and submitting bugs (Generates/Submits SQL INSERT statement)
//
CRaid::CRaid() : m_hstmt(SQL_NULL_HSTMT),pFieldHead(NULL),nNumOfAddedFields(0)
{
}

//
//
//~CRaid
//walk list of fields and delete each list entry
//
CRaid::~CRaid()
{
	
	RAIDFIELD* pCurrent = pFieldHead;
	RAIDFIELD* pNext = NULL;

	if(pFieldHead == NULL)
		return;

	while(pCurrent != NULL)
	{
		pNext = pCurrent->pNext;
		DELANDNULL(pCurrent);
		pCurrent = pNext;
	}

}

//
//
//CRaid::GetRaidRegKey
//Retrieves the value of a raid related regkey
//
bool CRaid::GetRaidRegKey(char* pszDsn,char* szSubKey,char* pszValue)
{
	char RaidRegKey[256];
	char* pszTemp = NULL;

	wsprintf(RaidRegKey,"Software\\ODBC\\ODBC.INI\\%s",pszDsn);
	
	if((pszTemp = ReadRegistry(RaidRegKey,szSubKey,FALSE)) == 0)
	{
		sprintf(szErrorMsg,"Raid database %s must first be opened using RAID.",pszDsn);
		return false;
	}
	else
	{
		strcpy(pszValue,pszTemp);
		return true;
	}
}

//
//
//CRaid::Open
//Retrieves connection info from registry and calls the standard open call
//
bool CRaid::Open(char* pszDsn)
{
	gosASSERT(pszDsn);
	
	char szServer[256];
	char szUserName[256];
	char szPassword[256];
	char szDatabase[256];

	if(!GetRaidRegKey(pszDsn,"Server",szServer))
		return false;

	if(!GetRaidRegKey(pszDsn,"RwUID",szUserName))
		return false;

	if(!GetRaidRegKey(pszDsn,"RwPW",szPassword))
		return false;
	
	if(!GetRaidRegKey(pszDsn,"Database",szDatabase))
		return false;

	strcpy(szRaidDb,szDatabase);

	return(CRaid::Open(szServer,szUserName,szPassword,szDatabase));

}

//
//
//CRaid::Open
//Establishes the connection and then sets up the statement handle
//
bool CRaid::Open(char* pszServer,char* pszUID,char* pszPWD,char* pszDatabase)
{

	if(CODBCSQL::Open(pszServer,pszUID,pszPWD,pszDatabase) == false)
		return false;

	strcpy(szRaidDb,pszDatabase);

	//Allocate the statement handle
	retcode = _SQLAllocHandle(SQL_HANDLE_STMT, m_hdbc, &m_hstmt);
	
	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_DBC,m_hdbc,szErrorMsg);
		return false;
	}

	return true;
}

//
//
//CRaid::Close
//Establishes the connection and then sets up the statement handle
//
bool CRaid::Close()
{
	//TODO: SQLFreeStatement?

	if(m_hstmt != SQL_NULL_HSTMT)
	{
		retcode = _SQLFreeHandle(SQL_HANDLE_STMT, m_hstmt);
	
		if (retcode != SQL_SUCCESS) 
		{
			errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
			return false;
		}
	}

	return CODBCSQL::Close();
}

//
//
//CRaid::SetRaidField
//Creates a field object and it to the list of fields to be inserted into the Raid database
//
bool CRaid::SetRaidField(char *pszColumn,char *pszValue,unsigned long ulColSize)
{
	//validate params
	gosASSERT(pszColumn != NULL);
	gosASSERT(pszValue != NULL);

	RAIDFIELD* newField= new (DefaultHeap) RAIDFIELD(pszColumn,pszValue);

	ListAdd(*newField);

	//bind the data member variable of the new raid field to the correct parameter in the SQL statement
	retcode = _SQLBindParameter(m_hstmt,nNumOfAddedFields,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_VARCHAR,ulColSize,0,newField->pszValue,0,0); 

	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}
	
	return true;
}

//
//
//CRaid::SetRaidField
//Creates a field object and it to the list of fields to be inserted into the Raid database
//NOTE: this inserts as char not as int!
//
bool CRaid::SetRaidField(char *pszColumn,int nValue)
{
	//validate param
	gosASSERT(pszColumn != NULL);

	RAIDFIELD* newField= new (DefaultHeap) RAIDFIELD(pszColumn,nValue);

	ListAdd(*newField);
	
	//bind the data member variable of the new raid field to the correct parameter in the SQL statement
	retcode = _SQLBindParameter(m_hstmt,nNumOfAddedFields,SQL_PARAM_INPUT,SQL_C_LONG,SQL_CHAR,1,0,&(newField->nValue),0,0);

	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}

	return true;
}

//
//
//CRaid::SetRaidDesc
//also adds description field 
//
//
bool CRaid::SetDescription(char* pszDesc)
{
	int nLen = strlen(pszDesc);

	//add field
	return SetRaidField(pszDefRaidFields[DESCRIPTION],pszDesc,nLen+1);
}

//
//
//CRaid::SetDefRaidFields
//Adds the default Raid fields common to all Raid bugs to the list 
//
bool CRaid::SetDefRaidFields(RAIDBUG& bug)
{
	if(!SetRaidField(pszDefRaidFields[STATUS],pszRaidStatus[bug.nStatus]))
		return false;
	if(!SetRaidField(pszDefRaidFields[ASSIGNEDTO],bug.pszAssignedTo))
		return false;
	if(!SetRaidField(pszDefRaidFields[TITLE],bug.pszTitle,255))
		return false;
	if(!SetRaidField(pszDefRaidFields[ISSUETYPE],pszRaidIssue[bug.nIssueType]))
		return false;
	if(!SetRaidField(pszDefRaidFields[SEVERITY],bug.nSeverity))
		return false;
	if(!SetRaidField(pszDefRaidFields[PRIORITY],bug.nPriority))
		return false;
	if(!SetRaidField(pszDefRaidFields[ACCESSIBILITY],pszYesOrNo[bug.nAccessibility]))
		return false;
	if(!SetRaidField(pszDefRaidFields[ENVIRONMENT],bug.pszEnvironment,255))
		return false;
	if(!SetRaidField(pszDefRaidFields[OPENEDBY],bug.pszOpenedBy))
		return false;
	if(!SetRaidField(pszDefRaidFields[OPENEDREV],bug.pszOpenedRev,20))
		return false;
	if(!SetRaidField(pszDefRaidFields[SOURCE],pszRaidSource[bug.nSource]))
		return false;
	if(!SetRaidField(pszDefRaidFields[SOURCEID],bug.pszSourceID))
		return false;
	if(!SetRaidField(pszDefRaidFields[HOWFOUND],pszRaidHowFound[bug.nHowFound]))
		return false;
	if(!SetRaidField(pszDefRaidFields[LANG],bug.pszLang))
		return false;

	return true;
}

//
//
//CRaid::SetCustFields
//Parse a comma delimited string of key-value pairs (key=value) where the key is the column name and the value is the data to be submitted for that column
//For now this assumes all entries are for raid dropdowns (varchar type with a column width of 32)
bool CRaid::SetCustFields(const char* pszCustFieldDesc)
{
	char szColumnName[256];
	char szValue[256];

	char* token = NULL;
	char seps[] = ";=";
	
	char szTemp[MAXBUFLEN];
	strcpy(szTemp,pszCustFieldDesc);

	token = strtok(szTemp,seps);
	
	while(token != NULL)
	{
		strcpy(szColumnName,token);

		//get value
		token = strtok(NULL,seps);
		
		//if key/value string is malformed
		if(token == NULL)
		{	
			setErrorString("The 'custom fields' setting appears to be incorrect.");
			return false;					
		}

		strcpy(szValue,token);
		
		//add to list
		if(!SetRaidField(szColumnName,szValue))
			return false;
		
		//get next column
		token = strtok(NULL,seps);
	}

	return true;
}

//
//
//CRaid::ListAdd
//Attaches a Raid field to the list of fields 
//
bool CRaid::ListAdd(RAIDFIELD& newField)
{
	RAIDFIELD* curField = pFieldHead;

	//if list is empty, add head
	if(pFieldHead == NULL)
	{
		pFieldHead = &newField; 
		nNumOfAddedFields++;
		return true;
	}

	//loop through list, if duplicate column encountered overwrite value
	while(curField->pNext != NULL)
	{
		if(strcmp(newField.pszColName,curField->pszColName) == 0)
		{
			if(curField->nFieldType != newField.nFieldType)
				return false;
			
			if(curField->nFieldType == STRINGTYPE)
			{
				delete [] curField->pszValue;
 				curField->pszValue = new (DefaultHeap) SQLCHAR[strlen((char*)newField.pszValue) + 1];
				strcpy((char*)curField->pszValue,(char*)newField.pszValue);
				return true;
			}
			else
			{
				curField->nValue = newField.nValue;
				return true;
			}
		}
		
		curField = curField->pNext; 
	}
	
	//not duplicate, add new field at the end of the list
	curField->pNext = &newField;
	nNumOfAddedFields++;

	return true;
}

//
//
//CRaid::Submit
//Generate the SQL statement from the list of fields and then execute the statement on the SQL Server
//
bool CRaid::Submit(char* szSubmit)
{
	//TODO; change prototype to include bugid passed in

	//make sure database connection exists
	gosASSERT(m_hdbc != SQL_NULL_HDBC); 

	//make sure there is something to submit (a list of fields exist)
	gosASSERT(pFieldHead != NULL);
	
	//prepare the sql statement
	RAIDFIELD* pCurrent = pFieldHead;
	
	SQLCHAR Statement1[MAXBUFLEN];
	SQLCHAR Statement2[MAXBUFLEN];

	strcpy((char*)Statement1,"INSERT INTO Bugs(");
	strcpy((char*)Statement2," VALUES(");

	//walk the list generating the SQL statement
	while(pCurrent != NULL)
	{
		strcat((char*)Statement1,pCurrent->pszColName);
		strcat((char*)Statement1,",");
		strcat((char*)Statement2,"?,");

		pCurrent = pCurrent->pNext;
	}
	
	//finish off statements (remove final commas and concat)
	int nSzLen = 0;

	nSzLen = strlen((char*)Statement1);
	Statement1[nSzLen-1] = ')';
	
	nSzLen = strlen((char*)Statement2);
	Statement2[nSzLen-1] = ')';

	strcat((char*)Statement1,(char*)Statement2);

	//OutputDebugString((char*)Statement1);

	//submit the statement
	retcode = _SQLExecDirect(m_hstmt, Statement1, SQL_NTS);
	
	if((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}

	//get bugid
	strcpy((char*)Statement1,"SELECT bugid FROM Bugs WHERE Title = ? ORDER BY bugid DESC");

	pCurrent = pFieldHead;

	//walk the list looking for title to generate SQL statement
	while(pCurrent != NULL)
	{
		if(strcmp(pCurrent->pszColName,"Title") == 0) 
			break;
		else
			pCurrent = pCurrent->pNext;
	}
	
	//clear statement
	retcode = _SQLFreeStmt(m_hstmt,SQL_UNBIND);

	if(retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}

	//bind parameter
	retcode = _SQLBindParameter(m_hstmt,1,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_VARCHAR,255,0,pCurrent->pszValue,0,0); 

	if (retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}
	
	//OutputDebugString((char*)Statement1);
	
	// Execute the statement.
	// BUGBUG: have to clear the statement before binding again?
	retcode = _SQLExecDirect(m_hstmt, Statement1, SQL_NTS);	
	
	if(retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}
	
	SQLINTEGER nBugID = 0;

	//TODO: check parameters
	retcode = _SQLBindCol(m_hstmt, 1, SQL_C_ULONG,&nBugID, 1, 0);
	
	if(retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}

	//Fetch the results
	retcode = _SQLFetch(m_hstmt);

	if(retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}
	
	sprintf(szSubmit,"Successfully submitted bug number %d to the %s RAID database.\n",nBugID,szRaidDb);

	//TODO: loop with SQL_FETCH error thingee until all dupes are caught
	if((retcode = _SQLFetch(m_hstmt)) != SQL_NO_DATA)
	{		
		char szTempBugID[256];
		char szTempBugList[MAXBUFLEN];
		
		itoa(nBugID,szTempBugID,10);
		strcpy(szTempBugList,szTempBugID);
			
		szTempBugID[0] = '\0';

		while((retcode = _SQLFetch(m_hstmt)) != SQL_NO_DATA)
		{
			itoa(nBugID,szTempBugID,10);
			strcat(szTempBugList,",");
			strcat(szTempBugList,szTempBugID);
		}

		//if there is more than one duplicate bug
		if(szTempBugID[0] != '\0')
			strcat(szSubmit,"This bug is a possible duplicate with bugs numbered:");
		else
			strcat(szSubmit,"This bug is a possible duplicate with bug number ");

		strcat(szSubmit,szTempBugList);
		strcat(szSubmit,".");
	}

	//release the col binding
	//TODO: do this here in close?
	//TODO: verify params
	retcode = _SQLFreeStmt(m_hstmt,SQL_UNBIND);

	if(retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}

	//close the cursor
	//TODO: verify this?
	retcode = _SQLFreeStmt(m_hstmt,SQL_CLOSE);

	if(retcode != SQL_SUCCESS) 
	{
		errHandler(SQL_HANDLE_STMT,m_hstmt,szErrorMsg);
		return false;
	}

	return true;
}

//
//
//RAIDFIELD
//Key/Value pair of Column and Value to be submitted to Raid
//
RAIDFIELD::RAIDFIELD(char* pszNewColName, char* pszNewVal) : nFieldType(STRINGTYPE), pNext(NULL)
{
	//validate params
	gosASSERT(pszNewColName != NULL);
	gosASSERT(pszNewVal != NULL);
	
	pszColName = new (DefaultHeap) char[strlen(pszNewColName) + 1];
	strcpy(pszColName,pszNewColName);

	pszValue = new (DefaultHeap) SQLCHAR[strlen(pszNewVal) + 1];
	strcpy((char*)pszValue,pszNewVal);
}

//
//
//RAIDFIELD
//Key/Value pair of Column and Value to be submitted to Raid
//
RAIDFIELD::RAIDFIELD(char* pszNewColName, int nNewVal) : nFieldType(INTEGERTYPE), pNext(NULL)
{
	//validate param
	gosASSERT(pszNewColName != NULL);

	pszColName = new (DefaultHeap) char[strlen(pszNewColName) + 1];
	strcpy(pszColName,pszNewColName);

	nValue = nNewVal;
}

//
//
//~RAIDFIELD
// delete allocated strings
//
RAIDFIELD::~RAIDFIELD()
{
	DELANDNULL(pszColName);

	if(nFieldType == STRINGTYPE)
		DELANDNULL(pszValue);
}

//
//
//CGOSRaid
//
//
CGOSRaid::CGOSRaid() 
{
}

//
//
//~CGOSRaid
//
//
CGOSRaid::~CGOSRaid()
{
}


//
//
//CGOSRaid::SetRaidDesc
//also adds description field 
//
//
bool CGOSRaid::SetDescription(char* pszDesc)
{
	gosASSERT(pszDesc != NULL);
	
	//TODO: rtf description field
	
	char szFileName[255];
	
	if(!WriteExceptionFile(szFileName))
		return false;
	
	strcat(pszDesc,"\n Gos Exception File: <");
	strcat(pszDesc,szFileName);
	strcat(pszDesc,">");
	
	return CRaid::SetDescription(pszDesc);
}

//
//
//CGOSRaid::WriteExceptionFile
//Writes the exception file to the location stored in pszExceptionFilePath to a file 
//File name used is returned in pszFileName
//
bool CGOSRaid::WriteExceptionFile(OUT char* pszFileName)
{	
	//Generate file name
		//Get user id
		
		//ANDY: why 32+1? and 31?
		char szUserName[32+1];
		strncpy( szUserName, gosGetUserName(), 31 );

		//Get todays date
		char* pszDate = GetDateYYYYMMDD();
		
		//Setup the sequential number to tag on the end
		
		char szFileNum[5]; //assumes no more than 9999 bugs by a single user in a day

		//create path/file
		strcpy(pszFileName,Environment.RaidFilePath);
		
		int nPathLen = strlen(Environment.RaidFilePath);
	
		if(pszFileName[nPathLen-1] != '\\')
		{
			pszFileName[nPathLen] = '\\';
			pszFileName[nPathLen+1] = '\0';
		}

		strcat(pszFileName,szUserName);
		strcat(pszFileName,"-");
		strcat(pszFileName,pszDate);
		strcat(pszFileName,"-");
					
		int		nFileLen = strlen(pszFileName);
		char*	pFileNum = &pszFileName[nFileLen];
		HANDLE	hFile = INVALID_HANDLE_VALUE;

		//loop looking for first file name not taken
		for(int nFileNum = 1; hFile == INVALID_HANDLE_VALUE ;nFileNum++)
		{
			//attach file number
			sprintf(szFileNum,"%04d.txt",nFileNum);
			strcpy(pFileNum,szFileNum);

			//attempt to create file
			hFile = CreateFile( pszFileName, GENERIC_WRITE, 0,NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, NULL );
			
			//Error already exists is the expected error if filename is taken; break for other ones
			if((hFile == INVALID_HANDLE_VALUE ) && (GetLastError() != ERROR_FILE_EXISTS))
			{
				sprintf(szErrorMsg,"Couldn't store exception file. Please make sure you have access to the directory ' %s '",Environment.RaidFilePath);
				return false;
			}
						
		}
				
		//prepare error message to be written out
		char* EmCRLF = ConvertToCRLF(ErrorMessage);
		
		//write file
		int cbToWrite = strlen(EmCRLF);
		DWORD cbWritten = 0;
		
		int fWritten = WriteFile( hFile, EmCRLF, cbToWrite, &cbWritten, NULL );
					
		//tidy up
		free(EmCRLF);
		CloseHandle(hFile);			
		
		//if whole message wasn't written or write failed, quit
		if((!fWritten) || (cbToWrite != cbWritten))
			return false;

	return true;
}

//
//
//GetDateYYYYMMDD
//Returns the date in the form YYYYMMDD with no '/'s
//
char* GetDateYYYYMMDD()
{
	static char Buffer[64];
	SYSTEMTIME st;
	GetLocalTime( &st );
	wsprintf( Buffer, "%04d%02d%02d",st.wYear,st.wMonth,st.wDay);
	return Buffer;
}

//
//
//GetShortConfig
//Returns a slimmed down machine config: processor, RAM, graphics card
//
extern char DisplayInfoText[128];

char* GetShortConfig()
{
	
	static char EnvString[512];

	//snag processor info
	if( HasAMD )
		sprintf(EnvString,"AMD %s",SpeedBuffer);
	else if( HasCYRIX )
		sprintf(EnvString,"CYRIX %s",SpeedBuffer);
	else
		sprintf(EnvString,"%s",SpeedBuffer);

	int nLen = strlen(EnvString) - 1; //remove trailing space
	EnvString[nLen++] = ',';
	EnvString[nLen++] = ' ';
	
	//snag physical memory
	MEMORYSTATUS ms;
	GlobalMemoryStatus( &ms );
	char* pszRAM = DecNumber(ms.dwTotalPhys);
	
	//skip up to the stuff in the parenthesis
	while(*pszRAM++ != '(');
	
	while(*pszRAM != ')')
		EnvString[nLen++] = *pszRAM++;

	EnvString[nLen++] = ',';
	EnvString[nLen++] = ' ';
	EnvString[nLen++] = '\0';

	//grab the current rasterizer
	char* pszVid = DisplayInfoText;
	
	//skip the width x height in the text
	while(*pszVid++ != ' ');

	strcat(EnvString,pszVid);

	return EnvString;
}