text: /*******************************************************************************
  
  Copyright (c) 2010, Perforce Software, Inc.  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.
  
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 PERFORCE SOFTWARE, INC. 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.
  
  *******************************************************************************/
  
  /*******************************************************************************
   * Name		: P4BridgeServer.cpp
   *
   * Author	: dbb
   *
   * Description	:  P4BridgeServer
   *
   ******************************************************************************/
  #include "StdAfx.h"
  #include "P4BridgeServer.h"
  #include "ConnectionManager.h"
  #include "P4Connection.h"
  
  #include <spec.h>
  #include <debug.h>
  #include <ignore.h>
  #include <hostenv.h>
  #include "ticket.h"
  #include "error.h"
  #include "errornum.h"
  #include "strarray.h"
  #include "P4BridgeEnviro.h"
  #include <stdexcept>
  #include <sstream>
  #include <iomanip>
  
  #define DELETE_OBJECT(obj) { if( obj != NULL ) { delete obj; obj = NULL; } }
  #define DELETE_ARRAY(obj)  { if( obj != NULL ) { delete[] obj; obj = NULL; } }
  
  #define HANDLE_EXCEPTION(ppErrStr) HandleException(__FILE__, __LINE__, __FUNCTION__, GetExceptionCode(), GetExceptionInformation(), ppErrStr)
  #define HANDLE_EXCEPTION_NOSTR() HandleException(__FILE__, __LINE__, __FUNCTION__, GetExceptionCode(), GetExceptionInformation(), NULL)
  #pragma comment(lib,"Version.lib")
  
  bool CheckErrorId(const ErrorId &eid, const ErrorId &tgt)
  {
      return eid.Subsystem() == tgt.Subsystem() && eid.SubCode() == tgt.SubCode();
  }
  
  bool CheckErrorId(Error  &e, const ErrorId &tgt)
  { 
  	if (e.Test())
  	{
  		// iterate through the ErrorIds in this Error
  		for (int i = 0; ; ++i) 
  		{
  			ErrorId    *eid = e.GetId(i);
  			if (eid == NULL)
  				break;
  			if (CheckErrorId(*eid, tgt) )
  			{
  				return true;
  			}
  		}
  	} 
  	return false;
  }
  
  P4BridgeEnviro P4BridgeServer::_enviro;
  ILockable P4BridgeServer::envLock;
  
  // This is were the pointer to the log callback is stored if set by the user.
  LogCallbackFn * P4BridgeServer::pLogFn = NULL;
  
  int HandleException_Static(unsigned int c, struct _EXCEPTION_POINTERS *e)
  {
  	return EXCEPTION_EXECUTE_HANDLER;
  }
  
  /******************************************************************************
  // LogMessage: no formatting version, avoids getting a %blah in message with no args
  //
  ******************************************************************************/
  int P4BridgeServer::LogMessageNoArgs(int log_level, const char * file, int line, const char * message)
  {
  	if (!pLogFn)
  		return 0;
  
  	__try
  	{
  		return (*pLogFn)(log_level, file, line, message);
  	}
  	__except (HandleException_Static(GetExceptionCode(), GetExceptionInformation()))
  	{
  		// bad ptr?
  		pLogFn = NULL;
  	}
  
  	return 0;
  }
  
  /******************************************************************************
  // LogMessage: Use the client logging callback function (if set) to log a 
  //   message in the callers log.
  ******************************************************************************/
  int P4BridgeServer::LogMessage(int log_level, const char * file, int line, const char * message, ...)
  {
  	if (pLogFn)
  	{
  		va_list args;
  		va_start(args, message);
  
  		int buffSize = 1024;
  		char* buff1 = NULL;
  
   		int len = -1;
  		while (len < 0)
  		{
  			DELETE_ARRAY(buff1)
  			buff1 = new char[buffSize];
  			len = vsnprintf_s( buff1, buffSize, buffSize - 1, message, args);
  			buffSize *= 2;
  		}
  
  		int ret = 0;
  
  		__try
  		{
  			ret = (*pLogFn)(log_level, file, line, buff1);
  		}
  		__except (HandleException_Static(GetExceptionCode(), GetExceptionInformation()))
  		{
  			// bad ptr?
  			pLogFn = NULL; 
  		}
  		DELETE_ARRAY(buff1)
  	
  		return ret;
  	}
  	return 0;
  }
  
  /*******************************************************************************
   *
   *  Default Constructer
   *
   *  Protected, should not be used by a client to create a P4BridgeServer.
   *
   ******************************************************************************/
  
  P4BridgeServer::P4BridgeServer(void) :
  	p4base(Type()),
  	isUnicode(-1),
  	useLogin(0),
  	supportsExtSubmit(0),
  	initialized(false),
  	pConnection(NULL),
  	charset(CharSetApi::NOCONV),
  	fileCharset(CharSetApi::NOCONV),
  	runThreadId(0),
  	pTransfer(NULL),
  	pParallelTransferCallbackFn(NULL)
  {
  }
  
  P4BridgeClient* P4BridgeServer::get_ui() 
  { 
  	LOG_ENTRY();
  	return getConnection()->getUi();
  }
  
  /*******************************************************************************
   *
   *  Constructer
   *
   *  Create a P4BridgeServer and connect to the specified P4 Server.
   *
   ******************************************************************************/
  
  P4BridgeServer::P4BridgeServer( const char *p4port, 
  								const char *user, 
  								const char *pass,
  								const char *ws_client) :
  	p4base(Type()),
  	isUnicode(-1),
  	useLogin(0),
  	supportsExtSubmit(0),
  	initialized(false),
  	pConnection(NULL),
  	charset(CharSetApi::NOCONV),
  	fileCharset(CharSetApi::NOCONV),
  	runThreadId(0),
  	pTransfer(NULL),
  	pParallelTransferCallbackFn(NULL)
  {
  	LOG_DEBUG3(4,"Creating a new P4BridgeServer on %s for user, %s, and client, %s", p4port, user, ws_client);
  	Locker.InitCritSection();
  
  	LOCK(&Locker);
  
  	disposed = 0;
  
  	isUnicode = -1;
  	useLogin = 0;
  	supportsExtSubmit = 0;
  	connecting = 0;
  
  	// Clear the the callbacks 
  	pTaggedOutputCallbackFn = NULL;
  	pErrorCallbackFn = NULL;
  	pInfoResultsCallbackFn = NULL;
  	pTextResultsCallbackFn = NULL;
  	pBinaryResultsCallbackFn = NULL;
  	pPromptCallbackFn = NULL;
  	pResolveCallbackFn = NULL;
  	pResolveACallbackFn = NULL;
  
  	// connect to the server using a untagged protocol
  	if (p4port)		this->p4port = p4port;
  	if (user)		this->user = user;
  	if (ws_client)	this->client = ws_client;
  	if (pass)		this->password = pass;
  }
  
  /*******************************************************************************
   *
   *  Destructor
   *
   *  Close the connection and free up resources.
   *
   ******************************************************************************/
  
  P4BridgeServer::~P4BridgeServer(void)
  {
  	if (disposed != 0)
  	{
  		return;
  	}
  	else
  	{
  		LOCK(&Locker); 
  	
  		disposed = 1;
  
  		// Clear the the callbacks 
  		pTaggedOutputCallbackFn = NULL;
  		pErrorCallbackFn = NULL;
  		pInfoResultsCallbackFn = NULL;
  		pTextResultsCallbackFn = NULL;
  		pBinaryResultsCallbackFn = NULL;
  		pPromptCallbackFn = NULL;
  		pResolveCallbackFn = NULL;
  		pResolveACallbackFn = NULL;
  		pParallelTransferCallbackFn = NULL;
  
  		close_connection();
  	
  		delete pConnection;
  	}
  
  	Locker.FreeCriticalSection();
  }
  
  /*******************************************************************************
   *
   *  connected
   *
   *  Connect to the specified P4 Server, create a UI.
   *
   ******************************************************************************/
  
  int P4BridgeServer::connected( P4ClientError **err )
  {
  	__try
  	{
  		return connected_int( err );
  	}
  	__except (HANDLE_EXCEPTION_NOSTR())
  	{
  	}
  	connecting = 0;
  
  	return 0;
  }
  
  void P4BridgeServer::setInitialized(bool _initialized)
  {
  	initialized = _initialized;
  }
  
  bool P4BridgeServer::isInitialized() const
  {
  	return initialized;
  }
  
  int P4BridgeServer::connected_int( P4ClientError **err )
  {
  	LOCK(&Locker);
  	LOG_ENTRY();
  
  	*err = NULL;
  
  	if((connecting) || (isInitialized()))
  	{
  		LOG_DEBUG2(4, "Connecting: %d, isInitialized(): %d", connecting, isInitialized());
  		return 1; // already connected
  	}
  	connecting = 1;
  	// Set the Unicode flag to unknown, to force a retest
  	isUnicode = -1;
  
  	apiLevel = -1;
  	useLogin = -1;
  	supportsExtSubmit = -1;
  
  	LOG_LOC();
  	if (GetServerProtocols(err))
  	{
  		p4debug.SetLevel("-vnet.maxwait=5");
  
  		setInitialized(true);
  
  		connecting = 0;
  
  		return 1;
  	}
  
  	LOG_LOC();
  	close_connection();
  
  	setInitialized(false);
  	connecting = 0;
  	return 0;
  }
  
  /*******************************************************************************
   *
   *  connect_and_trust
   *
   *  Connect to the specified P4 Server, create a UI, and establish a trust 
   *	 relationship.
   *
   ******************************************************************************/
  
  int P4BridgeServer::connect_and_trust( P4ClientError **err, char* trust_flag, char* fingerprint )
  {
  	__try
  	{
  		return connect_and_trust_int( err, trust_flag, fingerprint );
  	}
  	__except (HANDLE_EXCEPTION_NOSTR())
  	{
  	}
  	connecting = 0;
  
  	return 0;
  }
  
  int P4BridgeServer::connect_and_trust_int( P4ClientError **err, char* trust_flag, char* fingerprint )
  {
  	LOCK(&Locker); 
  
  	if (connecting || isInitialized())
  	{
  		return 1; // already connected
  	}
  	connecting = 1;
  	LOG_LOC();
  	P4Connection* pCon = getConnection();
  
  	char** args = new char*[2];
  	args[0] = "-d";
  
  	run_command( "trust", 0, 1, args, 1 );
  
  	args[0] = trust_flag;
  	args[1] = fingerprint;
  
  	if (!run_command( "trust", 0, 1, args, (fingerprint != NULL)?2:1 ))
  	{
  		P4ClientError *e = pCon->getUi()->GetErrorResults();
  		if ((e!= NULL) && (e->Severity >= E_FAILED))
  		{
  			*err = e;
  		}
  
  		disconnect();
  		return 0;
  	}
  
  	// Set the Unicode flag to unknown, to force a retest
  	isUnicode = -1;
  
  	apiLevel = -1;
  	useLogin = -1;
  	supportsExtSubmit = -1;
  
  	if (GetServerProtocols(err))
  	{
  		p4debug.SetLevel("-vnet.maxwait=5");
  
  		setInitialized(true);
  
  		connecting = 0;
  
  		return 1;
  	}
  
  	LOG_LOC();
  	close_connection();
  
  	setInitialized(false);
  	connecting = 0;
  
  	return 0;
  }
  
  /*******************************************************************************
   *
   * close_connection
   *
   *  Final disconnect from the P4 Server.
   *
   ******************************************************************************/
  
  int P4BridgeServer::close_connection()
  {
  	LOCK(&Locker); 
  	LOG_ENTRY();
  
  	// Close connections
  	Error e;
  	if (pConnection)
  	{
  		LOG_LOC();
  		pConnection->Final(&e);
  	}
  
  	// the ClientUser (P4BridgeClient) deletes the transfer object
  	// this implies that we're probably not managing the transfer object
  	// in the expected way due to the P4API.Net layer's configuration 
  	// abilities. Anyway, don't delete it.
  	pTransfer = NULL;
  
  	// should we check e?  if the connection was invalid Final() 
  	// will return a bad result, so ignore it and complete the cleanup
  #if 0
  	if (e.Test())
  	{
  		return 0;
  	}
  #endif
  
  	DELETE_OBJECT(pConnection);
  
  	// Set the Unicode flag to unknown, to force a retest
  	isUnicode = -1;
  
  	apiLevel = -1;
  	useLogin = -1;
  	supportsExtSubmit = -1;
  	setInitialized(false);
  
  	return 1;
  }
  
  /*******************************************************************************
   *
   * disconnect
   *
   *  Disconnect from the P4 Server after a command, but save protocols and other
   *	 settings.
   *
   ******************************************************************************/
  
  int P4BridgeServer::disconnect( void )
  {
  	LOCK(&Locker); 
  	LOG_ENTRY();
  
  	if (pConnection)
  	{
  		pConnection->Disconnect();
  		// don't delete it.  it's possible that someone would
  		// disconnect (p4api.net auto-disconnects after N seconds) 
  		// and fetch the results later.  This used to break a GetConfig()
  		// test in p4api.net, but refactoring how set_cwd works seems
  		// to have made the behavior identical without the explicit delete here
  	}
  
  	return 1;
  }
  
  /*******************************************************************************
   *
   *  get_charset
   *
   * Get the character set from the environment.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_charset( )
  {
  	LOG_ENTRY();
  	// TODO: store the string charset name instead of potentially regenerating a connection
  	return getConnection()->GetCharset().Text();
  }
  
  CharSetApi::CharSet GetDefaultCharSet()
  {
      switch (GetACP())
      {
          case 437:   return CharSetApi::WIN_US_OEM;
          case 737:	return CharSetApi::CP737;
          case 932:   return CharSetApi::SHIFTJIS;
          case 936:   return CharSetApi::CP936;
          case 949:   return CharSetApi::CP949;
          case 950:   return CharSetApi::CP950;
          case 1200:  return CharSetApi::UTF_16_LE_BOM;
          case 1201:  return CharSetApi::UTF_16_BE_BOM;
          case 1251:  return CharSetApi::WIN_CP_1251;
          case 1253:  return CharSetApi::CP1253;
          case 10000: return CharSetApi::MACOS_ROMAN;
          case 12000: return CharSetApi::UTF_32_LE_BOM;
          case 12001: return CharSetApi::UTF_32_BE_BOM;
          case 20866: return CharSetApi::KOI8_R;
          case 20932: return CharSetApi::EUCJP;
          case 28591: return CharSetApi::ISO8859_1;
          case 28595: return CharSetApi::ISO8859_5;
          case 28597: return CharSetApi::ISO8859_7;
          case 28605: return CharSetApi::ISO8859_15;
          case 65001: return CharSetApi::UTF_8;
  
          default:
          case 1252:  return CharSetApi::WIN_US_ANSI;
     }
  } 
  
  /*******************************************************************************
   *
   *  set_charset
   *
   * Set the character set for encoding Unicode strings for command parameters 
   *  and output. Optionally, a separate encoding can be specified for the 
   *  contents of files that are directly saved in the client's file system.
   *
   ******************************************************************************/
  
  string P4BridgeServer::set_charset( const char* c, const char * filec )
  {
  	CharSetApi::CharSet cs;
  	if (c)
  	{
  		// Lookup the correct enum for the specified character set for the API
  		cs = CharSetApi::Lookup( c );
  		if( cs < 0 )
  		{
  			StrBuf	m;
  			m = "Unknown or unsupported charset: ";
  			m.Append( c );
  
  			LOG_ERROR( m.Text() );
  			return m.Text();
  		}
  	}
  	else
  	{
  		cs = CharSetApi::UTF_8;
  		c = CharSetApi::Name(cs);
  	}
  
  	CharSetApi::CharSet filecs;
  
  	// Lookup the correct enum for the specified character set for file 
  	//  contents
  	if (filec)
  	{
  		filecs = CharSetApi::Lookup( filec );
  		if( filecs < 0 )
  		{
  			StrBuf	m;
  			m = "Unknown or unsupported charset: ";
  			m.Append( filec );
  
  			LOG_ERROR( m.Text() );
  			return m.Text();
  		}
  	}
  	else 
  	{
  		// default value
  		filecs = CharSetApi::WIN_US_ANSI;
  
  		LOG_LOC();
  		string filec = getConnection()->GetCharset().Text();
  
  		if (!filec.empty())
  		{
  			filecs = CharSetApi::Lookup(filec.c_str());
  			if ((int)filecs <= 0)
  			{
  				// not set, get a value based on the system code page
  				filecs = GetDefaultCharSet();
  			}
  		}
  	}
  	LOG_INFO1( "[P4] Setting charset: %s\n", CharSetApi::Name(cs) );
  
  	// record for reconnects
  	charset = cs;
  	fileCharset = filecs;
  
  	LOG_LOC();
  	getConnection()->SetCharset( cs, filecs );
  
  	return "";
  }
  
  /*******************************************************************************
   *
   * set_cwd
   *
   *  Set the working directory.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_cwd( const char* newCwd )
  {
  	// cache for later
  	pCwd = (newCwd) ? newCwd : "";
  	LOG_DEBUG2(4, "Setting 0x%llu cwd to %s", pConnection, pCwd.c_str());
  	LOG_DEBUG1(4, "P4CONFIG: %s", this->Get("P4CONFIG"));
  	// don't create a connection just for the CWD, but if the connection existed
  	// already and is unconnected, go ahead and delete it.  It will be re-created 
  	// on-demand
  	if (pConnection)
  	{
  		if (!pConnection->IsConnected())
  		{
  			DELETE_OBJECT(pConnection);	// pCwd will get set when the connection is created
  		}
  		else
  		{
  			pConnection->SetCwd(pCwd.c_str());
  		}
  	}
  
  }
  
  /*******************************************************************************
   *
   * get_cwd
   *
   *  Get the working directory.
   *
   ******************************************************************************/
  
  static StrBuf EmptStr("");
  
  string P4BridgeServer::get_cwd( void )
  {
  	LOG_LOC();
  	return getConnection()->GetCwd().Text();
  
  }
  
  // After the Run call is complete, with no exceptions thrown
  //  it is time to look for the "hidden" parallel sync errors - see job076982
  void checkForParallelError(int existingErrors, P4Connection* client, const char *cmd, P4BridgeClient* ui)
  {
  	// client does not clear its error count, so we need to detect that the error count changed
  	// but we never got any HandleError() calls in our ClientUser object (P4BridgeClient)
  	// we could check that it's a cmd that is parallel available (sync and submit), but 
  	// this is a little more future-friendly
  	if (existingErrors != client->GetErrors() && ui->GetErrorResults() == NULL)
  	{
  		ui->HandleError(E_FAILED, 0, "Error detected during parallel operation");
  	}
  }
  
  void P4BridgeServer::Run_int(P4Connection* client, const char *cmd, P4BridgeClient* ui)
  {
  	string* pErrorString = NULL;
  
  	__try
  	{
  		int existingErrors = client->GetErrors();
  		LOG_DEBUG1(4, "Run_int is running '%s'", cmd);
  		client->Run(cmd, ui);
  		LOG_DEBUG1(4, "Run_int returned from '%s'", cmd);
  		checkForParallelError(existingErrors, client, cmd, ui);
  	}
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		if (ui)
  		{
  			ui->HandleError( E_FATAL, 0, pErrorString->c_str() );
  			DELETE_OBJECT(pErrorString);
  		}
  	}
  }
  
  
  
  char *GetInfo(char* lpstrVffInfo, char *InfoItem)
  {
      char*   szResult = new char[256];
      char    szGetName[256];
      LPSTR   lpVersion;        // String pointer to Item text
      DWORD   dwVerHnd=0;       // An 'ignored' parameter, always '0'
      UINT    uVersionLen;
      BOOL    bRetCode;
  
      // Get a codepage from base_file_info_sctructure
      lstrcpy(szGetName, "\\VarFileInfo\\Translation");
      uVersionLen   = 0;
      lpVersion     = NULL;
      bRetCode = VerQueryValue((LPVOID)lpstrVffInfo,
              (LPSTR)szGetName,
              (void **)&lpVersion,
              (UINT *)&uVersionLen);
      if ( bRetCode && uVersionLen && lpVersion) 
  	{
          sprintf_s(szResult, 256, "%04x%04x", (WORD)(*((DWORD *)lpVersion)),
              (WORD)(*((DWORD *)lpVersion)>>16));
      }
      else {
          // 041904b0 is a very common one, because it means:
          //   US English/Russia, Windows MultiLingual characterset
          // Or to pull it all apart:
          // 04------        = SUBLANG_ENGLISH_USA
          // --09----        = LANG_ENGLISH
          // --19----        = LANG_RUSSIA
          // ----04b0 = 1200 = Codepage for Windows:Multilingual
          lstrcpy(szResult, "041904b0");
      }
  
      // Add a codepage to base_file_info_sctructure
      sprintf_s (szGetName, 256, "\\StringFileInfo\\%s\\", szResult);
      // Get a specific item
      lstrcat (szGetName, InfoItem);
  
      uVersionLen   = 0;
      lpVersion     = NULL;
      bRetCode = VerQueryValue((LPVOID)lpstrVffInfo,
              (LPSTR)szGetName,
              (void **)&lpVersion,
              (UINT *)&uVersionLen);
      if ( bRetCode && uVersionLen && lpVersion) 
  	{
          lstrcpy(szResult, lpVersion);
      }
      else 
  	{
  		delete[] szResult;
          szResult = NULL;
      }
      
  	// if szResult is NULL return an empty string
  	return szResult == nullptr ? "" : szResult;
  }
  
  /*******************************************************************************
   *
   * run_command
   *
   * Run a command using the supplied parameters. The command can either be run 
   *  in tagged or untagged protocol. If the target server supports Unicode, the 
   *  strings in the parameter list need to be encoded in the character set 
   *  specified by a previous call to set_charset().
   *
   ******************************************************************************/
  
  int P4BridgeServer::run_command( const char *cmd, int cmdId, int tagged, char **args, int argc )
  {
  	P4ClientError *err = NULL;
  	LOG_ENTRY();
  
  	if( connected( &err ) )
  	{
  		LOG_LOC();
  		DELETE_OBJECT(err)
  	}
  	Error e;
  
  	StrBuf msg;
  
  	P4Connection* connection = getConnection(cmdId);
  	if(!connection)
  	{
  		LOG_ERROR1( "Error getting connection for command: %d", cmdId );
  		return 0;
  	}
  
  	P4BridgeClient* ui = connection->getUi();
  	if (ui)
  	{
  		if (err != NULL)
  		{
  			// couldn't connect
  			ui->HandleError( err );
  			return 0;
  		}
  		ui->clear_results();
  	}
  	else
  	{	
  		LOG_ERROR("connection did not have a P4BridgeClient ui object");
  		return 0;
  	}
  
  	connection->IsAlive(1);
  
  	// Connect to server
  	if(connection->Dropped())
  	{
  		connection->Final(&e);
  		if( e.Test() )
  		{
  			ui->HandleError(&e);
  			return 0;
  		}
  		connection->clientNeedsInit = 1;
  	}
  	if (connection->clientNeedsInit)
  	{
  		connection->Init( &e );
  		if( e.Test() )
  		{
  			ui->HandleError(&e);
  			return 0;
  		}
  		connection->clientNeedsInit = 0;
  	}
  	bool setProdName = pProgramName.empty();
  	bool setProdVer = pProgramVer.empty();
  
  	char* pModPath = NULL;
  	if (setProdName || setProdVer)
  	{
  		// need to get the module path to set either the name and/or version
  		pModPath = new char[MAX_PATH];
  		if ( GetModuleFileName( NULL, pModPath, MAX_PATH ) == 0 )
  		{
  			delete[] pModPath ;
  			pModPath = NULL;
  		}
  	}
  	// Label Connections for p4 monitor
  
  	if (setProdVer && pModPath)
  	{
  		DWORD sz = GetFileVersionInfoSize(pModPath, NULL);
  		UINT BufLen;
  
  		if (sz > 0)
  		{
  			VS_FIXEDFILEINFO *pFileInfo;
  			char* lpData = new char[sz];
  			if (GetFileVersionInfo( pModPath, 0, sz, lpData  ))
  			{
  				pProgramVer = GetInfo(lpData, "ProductVersion");
  
  				if (pProgramVer.empty())
  				{
  					if (VerQueryValue( lpData, "\\", (LPVOID *) &pFileInfo, (PUINT)&BufLen ) ) 
   					{
  						WORD MajorVersion = HIWORD(pFileInfo->dwProductVersionMS);
  						WORD MinorVersion = LOWORD(pFileInfo->dwProductVersionMS);
  						WORD BuildNumber = HIWORD(pFileInfo->dwProductVersionLS);
  						WORD RevisionNumber = LOWORD(pFileInfo->dwProductVersionLS);
  
  						std::stringstream ss;
  						ss << MajorVersion << "." << MinorVersion << "." << BuildNumber << "." << RevisionNumber;
  						pProgramVer = ss.str();
  					}
  				}
  			}
  			if (setProdName)
  			{
  				char* prodName = GetInfo(lpData, "ProductName");
  				if (prodName)
  				{
  					pProgramName = prodName;
  					setProdName = false;
  				}
  			}
  			free (lpData);
  		}
  #ifdef _DEBUG
  		// in debug versions use the error message saying why we couldn't get a version string
  		// as the version string.
  		else
  		{
  			LPTSTR errorText = NULL;
  
  			DWORD err = GetLastError();
  			DWORD retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
                             FORMAT_MESSAGE_FROM_SYSTEM|
                             FORMAT_MESSAGE_ARGUMENT_ARRAY,
                             NULL,
                             err,
                             LANG_NEUTRAL,
                             (LPTSTR)&errorText,
                             0,
                             NULL );		
  			pProgramVer = errorText;
  			LocalFree(errorText);
  		}
  #endif
  	}
  	if (setProdName && pModPath)
  	{
  		// need to set the product name, and we're not going to do 
  		// later when we set the product version, and we have the
  		// path of the module that loaded the dll
  		int idx1 = 0;
  		int idx2 = 0;
  		while ((idx1 < MAX_PATH) && (pModPath[idx1] != '\0'))
  		{
  			if (pModPath[idx1++] == '\\')
  			{
  				// interested in the character after the last '\\'
  				idx2 = idx1;
  			}
  		}
  		if (idx2 < idx1)
  		{
  			pProgramName = new char[(idx1 - idx2) + 1];
  			idx1 = idx2;
  			while ((idx1 < MAX_PATH) && (pModPath[idx1] != '\0'))
  			{
  				pProgramName[idx1-idx2] = pModPath[idx1++];
  			}
  			pProgramName[idx1-idx2] = '\0';
  		}
  	}
  	DELETE_ARRAY(pModPath);
  
  	if (!pProgramName.empty())
  		connection->SetProg( pProgramName.c_str() );
  	else
  		connection->SetProg( "dot-net-api-p4" );
  
  	if (!pProgramVer.empty())
  		connection->SetVersion( pProgramVer.c_str() );
  	else
  		connection->SetVersion( "NoVersionSpecified"); //Nobody liked "1.0" );
  
  	connection->SetVar(P4Tag::v_tag, tagged ? "yes" : 0);
  
  	connection->SetArgv( argc, args );
  	connection->SetBreak(connection);
  	if (pParallelTransferCallbackFn) {
  		// make a transfer object?
  		if (!pTransfer)
  			pTransfer = new ParallelTransfer(this);
  		ui->SetTransfer(pTransfer);
  	} 
  	else
  	{
  		// make sure we clear it
  		ui->SetTransfer(NULL);
  	}
  
  	Run_int(connection, cmd, ui);
  
  	P4ClientError* errors = ui->GetErrorResults();
  	if (errors != NULL)
  	{
  		int maxSeverity = errors->MaxSeverity();
  		if ( maxSeverity >= 3 )
  		{
  			return 0;
  		}
  	}
  	if(connection->Dropped())
  	{
  		connection->Final(&e);
  		if( e.Test() )
  		{
  			ui->HandleError(&e);
  		}
  		connection->clientNeedsInit = 1;
  	}
  
  	if (connection->IsAlive() == 0) 
  	{
  		// the command was cancelled
  		return 0;
  	}
  
  	// Fix for Job085941
  	// In parallel activities, error reports may "get eaten" but the api now provides the GetErrors() call
  	//  which returns a non-zero error count if one of the "transmit" threads has had a problem.
  	//if (connection->GetErrors())
  	//{
  	//	return 0;
  	//}
  
  	return 1;
  }
  
  P4Connection* P4BridgeServer::getConnection(int id /*= 99999999*/)
  {
  	if (!pConnection)
  	{
  		LOG_LOC();
  		pConnection = new P4Connection(this, id);
  		if (!client.empty()) pConnection->SetClient(client.c_str());
  		if (!user.empty()) pConnection->SetUser(user.c_str());
  		if (!p4port.empty()) pConnection->SetPort(p4port.c_str());
  		LOG_DEBUG2(4, "getting connection with P4PORT=%s/%s", p4port.c_str(), pConnection->GetPort().Text());
  		if (!password.empty()) pConnection->SetPassword(password.c_str());
  		if (!pProgramName.empty()) pConnection->SetProg(pProgramName.c_str());
  		if (!pProgramVer.empty()) pConnection->SetVersion(pProgramVer.c_str());
  		if (!pCwd.empty()) pConnection->SetCwd(pCwd.c_str());
  		if (!ticketFile.empty()) pConnection->SetTicketFile(ticketFile.c_str());
  
  		pConnection->SetProtocol("specstring", "");
  		pConnection->SetProtocol("enableStreams", "");
  		pConnection->SetProtocol("enableGraph", "");
  		pConnection->SetProtocol("wingui", "999");
  		pConnection->SetProtocol("unicode", "");
  
  		// also set the extraProtocols
  		for (std::map<string, string>::iterator it = extraProtocols.begin(); it != extraProtocols.end(); ++it)
  			pConnection->SetProtocol(it->first.c_str(), it->second.c_str());
  
  		// Set the character set for the untagged client
  		pConnection->SetCharset(charset, fileCharset);
  	}
  	else
  	{
  		// TODO: eliminate cmdId?
  		// just set the cmdId
  		pConnection->cmdId = id;
  	}
  	return pConnection;
  }
  
  void P4BridgeServer::cancel_command(int cmdId) 
  {
  	LOG_ENTRY();
  	getConnection()->cancel_command();
  }
  	
  bool P4BridgeServer::IsConnected()
  {
  	return pConnection && pConnection->IsConnected();
  }
  
  int P4BridgeServer::GetServerProtocols(P4ClientError **err)
  {
  	LOG_ENTRY();
  
  	if (isUnicode >= 0)
  	{
  		// already read the protocols
  		return 1;
  	}
  
  	LOG_LOC();
  	// set to 0 for now so we don't call this again when running the help 
  	//   command to get the protocols
  	isUnicode = 0;
  
  	// running the 'help' command on the server is the only command that 
  	//   does not lock any tables on the server, so it has the least impact.
  
  	P4Connection* pCon = getConnection();
  
  	// abort if we can't proceed (need at least to get a value for server2)
  	if (!run_command("help", 0, 1, NULL, 0))
  	{
  		LOG_DEBUG(4, "run help command failed");
  		// store the error
  		*err = pCon->getUi()->GetErrorResults();
  
  		// clear the error pointer or it will get deleted when/if the connection is
  		// closed due to the error
  		pCon->getUi()->ClearErrorResults();
  
  		// even if it failed, it may have gotten enough protocol information to proceed
  		// this can happen in situations where the server is so locked down that even "help" requires a login
  		// note that the GetProtocol() return pointer is only valid until the next GetProtocol call
  		StrPtr* server2 = pCon->GetProtocol("server2");
  		if (!server2 || server2->Length() == 0 || server2->Atoi() == 0)
  		{
  			disconnect();
  			return 0;
  		}
  	}
  
  	// Check server level
  	{
  		StrPtr *server2 = pCon->GetProtocol("server2");
  		apiLevel = (server2) ? server2->Atoi() : 0;
  
  		// Login/logout capable [2004.2 higher]
  		if (apiLevel >= SERVER_SECURITY_PROTOCOL) {
  			useLogin = 1;
  		}
  		else
  		{
  			useLogin = 0;
  		}
  	}
  
  	// Supports new submit options [2006.2 higher]
  	if ( apiLevel >= SERVER_EXTENDED_SUBMIT ) {
  		supportsExtSubmit = 1;
  	}
  	else
  	{
  		supportsExtSubmit = 0;
  	}
  
  	// check the unicode setting
  	{
  		StrPtr *unicode = pCon->GetProtocol(P4Tag::v_unicode);
  		if (unicode && unicode->Length() && unicode->Atoi())
  		{
  			isUnicode = 1;
  		}
  		else
  		{
  			isUnicode = 0;
  		}
  	}
  
  	// LOG_DEBUG4(0, "api, useLogin, isUnicode, supportsExtSubmit: %d, %d, %d", apiLevel, useLogin, isUnicode, supportsExtSubmit);
  	return 1;
  }
  
  /*******************************************************************************
   *
   * unicodeServer
   *
   * Does the connected server support unicode? If already determined, return the
   *  cached results, otherwise issue a help command and query the server to see
   *  if Unicode support is enabled.
   *
   ******************************************************************************/
  
  int P4BridgeServer::unicodeServer(  )
  {
  	P4ClientError* err = NULL;
  	GetServerProtocols(&err);
  	DELETE_OBJECT(err);
  
  	return isUnicode;
  }
  
  /*******************************************************************************
   *
   * APILevel
   *
   * The API level the connected server supports If already determined, return the
   *  cached results, otherwise issue a help command and query the server to see 
   *  what protocols the server supports.
   *
   ******************************************************************************/
  
  int P4BridgeServer::APILevel(  )
  {
  	P4ClientError* err = NULL;
  	GetServerProtocols(&err);
  	DELETE_OBJECT(err);
  	
  	return apiLevel;
  }
  
  /*******************************************************************************
   *
   * UseLogin
   *
   * Does the connected server require the login command be used? If already 
   *  determined, return the cached results, otherwise issue a help command and 
   *  query the server to see if Unicode support is enabled.
   *
   ******************************************************************************/
  
  int P4BridgeServer::UseLogin()
  {
  	LOG_LOC();
  	P4ClientError* err = NULL;
  	GetServerProtocols(&err);
  	DELETE_OBJECT(err);
  	
  	return useLogin;
  }
  
  //Does the connected sever support extended submit options (2006.2 higher)?
  /*******************************************************************************
   *
   * SupportsExtSubmit
   *
   * Does the connected server support extended submit options (2006.2 higher)? 
   *  If already determined, return the cached results, otherwise issue a help 
   *  command and query the server to see if Unicode support is enabled.
   *
   ******************************************************************************/
  int P4BridgeServer::SupportsExtSubmit()
  {
  	P4ClientError* err = NULL;
  	GetServerProtocols(&err);
  	DELETE_OBJECT(err);
  	
  	return supportsExtSubmit;
  }
  
  /*******************************************************************************
   *
   * SetConnection
   *
   * Set some or all of the parameters used for the connection.
   *
   ******************************************************************************/
  
  void  P4BridgeServer::set_connection(const char* newPort, 
  									const char* newUser, 
  									const char* newPassword, 
  									const char* newClient)
  {
  	// close the connection to force reconnection with new value(s)
  	LOG_ENTRY();
  	close_connection();
  
  	if (newPort)
  	{
  		this->p4port = newPort;
  	}
  
  	if (newUser)
  	{
  		this->user = newUser;
  	}
  
  	if (newPassword)
  	{
  		this->password = newPassword;
  	}
  
  	if (newClient)
  	{
  		this->client = newClient;
  	}
  }
  
  /*******************************************************************************
   *
   * set_client
   *
   * Set the workspace used for the connection.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_client( const char* newVal )
  {
  	// close the connection to force reconnection with new value(s)
  	LOG_ENTRY();
  	this->client = (newVal ? newVal : "");
  	if (pConnection)
  		pConnection->SetClient(this->client.c_str());
  }
  
  /*******************************************************************************
   *
   * set_user
   *
   * Set the user name used for the connection.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_user( const char* newVal )
  {
  	// close the connection to force reconnection with new value(s)
  	LOG_ENTRY();
  	this->user = (newVal ? newVal : "");
  	// close_connection();
  	if (pConnection)
  		pConnection->SetUser(this->user.c_str());
  }
  
  
  /*******************************************************************************
   *
   * set_port
   *
   * Set the port (hostname:portnumber) used for the connection.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_port( const char* newVal )
  {
  	LOG_ENTRY();
  	// close the connection to force reconnection with new value(s)
  	close_connection();
  	this->p4port = (newVal ? newVal : "");
  }
  
  /*******************************************************************************
   *
   * set_password
   *
   * Set the password used for the connection.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_password( const char* newVal )
  {
  	// close the connection to force reconnection with new value(s)
  	LOG_ENTRY();
  	this->password = (newVal ? newVal : "");
  	if (pConnection)
  		pConnection->SetPassword(this->password.c_str());
  }
  
  /*******************************************************************************
   *
   * set_ticketFile
   *
   * Set the ticket file used for the connection.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_ticketFile(const char* newVal)
  {
  	// close the connection to force reconnection with new value(s)
  	LOG_ENTRY();
  	this->ticketFile = (newVal ? newVal : "");
  	if (pConnection)
  		pConnection->SetTicketFile(this->ticketFile.c_str());
  }
  
  /*******************************************************************************
   *
   * set_programName
   *
   * Set the program name used for the connection.
   *
   ****************************************************if (pConnection)**************************/
  
  void P4BridgeServer::set_programName( const char* newVal )
  {
  	pProgramName = (newVal ? newVal : "");
  }
  
  /*******************************************************************************
   *
   * set_programVer
   *
   * Set the program version used for the connection.
   *
   ******************************************************************************/
  
  void P4BridgeServer::set_programVer( const char* newVal )
  {
  	pProgramVer = (newVal ? newVal : "");
  }
  
  /*******************************************************************************
   *
   * get_client
   *
   *  Get the workspace used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_client()
  {
  	LOG_ENTRY();
  	return getConnection()->GetClient().Text();
  }
  
  /*******************************************************************************
   *
   * get_user
   *
   *  Get the user name used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_user()
  {
  	LOG_ENTRY();
  	return getConnection()->GetUser().Text();
  }
  
  /*******************************************************************************
   *
   * get_port
   *
   *  Get the user port used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_port()
  {
  	LOG_ENTRY();
  	return getConnection()->GetPort().Text();
  }
  
  /*******************************************************************************
   *
   * get_password
   *
   *  Get the password used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_password()
  {
  	LOG_ENTRY();
  	return getConnection()->GetPassword().Text();
  }
  
  /*******************************************************************************
   *
   * get_ticketFile
   *
   *  Get the ticket file used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_ticketFile()
  {
  	LOG_ENTRY();
  	return ticketFile;
  }
  
  /*******************************************************************************
   *
   * get_programName
   *
   *  Get the program name used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_programName()
  {
  	return pProgramName;
  }
  
  /*******************************************************************************
   *
   * get_programVer
   *
   *  Get the program version used for the connection.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_programVer()
  {
  	return pProgramVer;
  }
  
  /*******************************************************************************
   *
   * get_config
   *
   *  Get the config file used for the connection, if any.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_config_Int(const char * cwd)
  {
  	// NOTE: do not use _enviro, this is a hypothetical question about a directory
  	Enviro enviroLocal;
  	LOG_LOC();
  	// update (not set) P4CONFIG to _enviro's.  This allows API users to use "Update"
  	// to alter the P4CONFIG locally without setting in the system registry
  	LOCK(&envLock);
  	// if the P4CONFIG for the env is null, don't bother updating enviroLocal
  	LOG_DEBUG1(4, "_enviro.Get(P4CONFIG) = %s", _enviro.Get("P4CONFIG"));
  	const char* curConfig = _enviro.Get("P4CONFIG");
  	if (curConfig != NULL)
  		enviroLocal.Update("P4CONFIG", curConfig);
  	// reload the configuration
  	LOG_DEBUG1(4, "Set enviroLocal to %s", cwd);
  	enviroLocal.Config(StrRef(cwd));
  
  	StrBuf sb = enviroLocal.GetConfig();
  	LOG_DEBUG1(4, "enviroLocal config is %s", sb.Text());
  
  	if (sb == "noconfig")
  	return sb.Text();
  
  	const StrArray* ret = enviroLocal.GetConfigs();
  	const StrBuf* sbp = ret->Get(0);
  	LOG_DEBUG1(4, "enviroLocal config is %s", sbp->Text());
  		return sbp->Text();
  }
  
  string P4BridgeServer::get_config(const char * cwd)
  {
  	LOG_ENTRY();
  	return P4BridgeServer::get_config_Int(cwd);
  }
  
  /*******************************************************************************
   *
   * get_config
   *
   *  Get the config file used for the connection, if any.
   *
   ******************************************************************************/
  
  string P4BridgeServer::get_config_Int()
  {
  	LOG_ENTRY();
  	P4ClientError *err = NULL;
  	if ( !connected( &err ) )
  	{
  		return "";
  	}
  
  	P4Connection* pCon = getConnection();
  	// if this seems weird, it's probably because it is
  	// pCon may actually be disconnected, as this class
  	// considers "connected" as "i got the protocol data 
  	// but may not actually be talking to the server".
  	// The config info is read when the Client gets created,
  	// and apparently we want something more responsive
  	LOG_DEBUG1(4, "pCon CWD: %s", pCon->GetCwd().Text());
  	const StrPtr& ret = pCon->GetConfig();
  	StrBuf sb = pCon->GetConfig(); ;// = ret;
  
  	if (sb == "noconfig")
  		return ret.Text();
  	
  	const StrArray* retA = pCon->GetConfigs();
  	const StrBuf* sbp = retA->Get(0);
  	LOG_DEBUG1(4, "pCon CWD: %s", sbp->Text());
  	return sbp->Text();
  }
  
  string P4BridgeServer::get_config()
  {
  	return P4BridgeServer::get_config_Int();
  }
  
  /*******************************************************************************
   *
   *  HandleException
   *
   *  Handle any platform exceptions. The Microsoft Structured Exception Handler
   *      allows software to catch platform exceptions such as array overrun. The
   *      exception is logged, but the application will continue to run.
   *
   ******************************************************************************/
  
  int P4BridgeServer::HandleException(const char* fname, unsigned int line, const char* func, unsigned int c, struct _EXCEPTION_POINTERS *e, string** ppErrorString)
  {
  	if (!this->disposed) // hopefully didn't get called on an already deleted object
  	{
  		unsigned int code = c;
  		struct _EXCEPTION_POINTERS *ep = e;
  
  		// Log the exception
  		const char * exType = "Unknown";
  
  		switch (code)
  		{
  		case EXCEPTION_ACCESS_VIOLATION:
  			exType = "EXCEPTION_ACCESS_VIOLATION\r\n";
  			break;
  		case EXCEPTION_DATATYPE_MISALIGNMENT:
  			exType = "EXCEPTION_DATATYPE_MISALIGNMENT\r\n";
  			break;
  		case EXCEPTION_BREAKPOINT:
  			exType = "EXCEPTION_BREAKPOINT\r\n";
  			break;
  		case EXCEPTION_SINGLE_STEP:
  			exType = "EXCEPTION_SINGLE_STEP\r\n";
  			break;
  		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
  			exType = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED\r\n";
  			break;
  		case EXCEPTION_FLT_DENORMAL_OPERAND:
  			exType = "EXCEPTION_FLT_DENORMAL_OPERAND\r\n";
  			break;
  		case EXCEPTION_FLT_DIVIDE_BY_ZERO:
  			exType = "EXCEPTION_FLT_DIVIDE_BY_ZERO\r\n";
  			break;
  		case EXCEPTION_FLT_INEXACT_RESULT:
  			exType = "EXCEPTION_FLT_INEXACT_RESULT\r\n";
  			break;
  		case EXCEPTION_FLT_INVALID_OPERATION:
  			exType = "EXCEPTION_FLT_INVALID_OPERATION\r\n";
  			break;
  		case EXCEPTION_FLT_OVERFLOW:
  			exType = "EXCEPTION_FLT_OVERFLOW\r\n";
  			break;
  		case EXCEPTION_FLT_STACK_CHECK:
  			exType = "EXCEPTION_FLT_STACK_CHECK\r\n";
  			break;
  		case EXCEPTION_FLT_UNDERFLOW:
  			exType = "EXCEPTION_FLT_UNDERFLOW\r\n";
  			break;
  		case EXCEPTION_INT_DIVIDE_BY_ZERO:
  			exType = "EXCEPTION_INT_DIVIDE_BY_ZERO\r\n";
  			break;
  		case EXCEPTION_INT_OVERFLOW:
  			exType = "EXCEPTION_INT_OVERFLOW\r\n";
  			break;
  		case EXCEPTION_PRIV_INSTRUCTION:
  			exType = "EXCEPTION_PRIV_INSTRUCTION\r\n";
  			break;
  		case EXCEPTION_IN_PAGE_ERROR:
  			exType = "EXCEPTION_IN_PAGE_ERROR\r\n";
  			break;
  		case EXCEPTION_ILLEGAL_INSTRUCTION:
  			exType = "EXCEPTION_ILLEGAL_INSTRUCTION\r\n";
  			break;
  		case EXCEPTION_NONCONTINUABLE_EXCEPTION:
  			exType = "EXCEPTION_NONCONTINUABLE_EXCEPTION\r\n";
  			break;
  		case EXCEPTION_STACK_OVERFLOW:
  			exType = "EXCEPTION_STACK_OVERFLOW\r\n";
  			break;
  		case EXCEPTION_INVALID_DISPOSITION:
  			exType = "EXCEPTION_INVALID_DISPOSITION\r\n";
  			break;
  		case EXCEPTION_GUARD_PAGE:
  			exType = "EXCEPTION_GUARD_PAGE\r\n";
  			break;
  		case EXCEPTION_INVALID_HANDLE:
  			exType = "EXCEPTION_INVALID_HANDLE\r\n";
  			break;
  		default:
  			printf("UNKNOWN EXCEPTION\r\n");
  			break;
  		}
  		
  		std::stringstream ss;
  
  		ss << fname << "(" << line << "): " << func << " : Exception Detected: (" 
  			"0x" << std::uppercase << std::setfill('0') << std::setw(4) << std::hex << code << ")" << exType;
  		LOG_ERROR(ss.str().c_str());
  		if (ppErrorString)
  		{
  			*ppErrorString = new string();
  			**ppErrorString = ss.str().c_str();
  		}
  	}
  
  	return EXCEPTION_EXECUTE_HANDLER;
  }
  
  int P4BridgeServer::sHandleException(unsigned int c, struct _EXCEPTION_POINTERS *e)
  {
  	return EXCEPTION_EXECUTE_HANDLER;
  }
  
  const char* P4BridgeServer::Get_Int( const char *var )
  {
  	LOCK(&envLock);
  	return _enviro.Get( var );
  }
  
  const char* P4BridgeServer::Get( const char *var )
  {
  	return P4BridgeServer::Get_Int( var );
  }
  
  void P4BridgeServer::Set_Int( const char *var, const char *value )
  {
  	LOCK(&envLock);
  
  	// Enviro is a little weird, if you set a NULL value it deletes from the
  	// registry but does not clear the symbol table value (it sort of does,
  	// but the value is still there).
  	Error e;
  	_enviro.Set( var, value, &e );
  	// workaround for P4-16150, also call Update to modify the cache
  	_enviro.Update(var, value);
  
  	if( e.Test() )
  	{
  		return;
  	}
  }
  
  void P4BridgeServer::Set( const char *var, const char *value )
  {
  	__try
  	{
  		return P4BridgeServer::Set_Int( var, value );
  	}  
  	__except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation()))
  	{
  	}
  }
  
  void P4BridgeServer::Update_Int( const char *var, const char *value )
  {
  	LOCK(&envLock);
  	_enviro.Update( var, value);;
  }
  
  void P4BridgeServer::Update( const char *var, const char *value )
  {
  	__try
  	{
  		return P4BridgeServer::Update_Int( var, value );
  	}  
  	__except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation()))
  	{
  	}
  }
  
  void P4BridgeServer::Reload_Int()
  {
  	LOCK(&envLock);
  	_enviro.Reload();
  }
  
  void P4BridgeServer::Reload()
  {
  	__try
  	{
  		return P4BridgeServer::Reload_Int();
  	}  
  	__except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation()))
  	{
  	}
  }
  
  void P4BridgeServer::SetProtocol_Int(const char *var, const char *value)
  {
  	// Note: this must be called before connecting or the server will ignore
  	//       and we only do that when getConnection() is called
  	LOG_ENTRY();
  	// save for new connections
  	extraProtocols[var] = value;
  	// if one has already been created, set it there too (might not be connected, just the object)
  	if (pConnection)
  	{
  		if (pConnection->IsConnected())
  		{
  			LOG_DEBUG2(4, "Trying to set %s=%s on and active connection, need to disconnect first", var, value);
  		}
  		pConnection->SetProtocol(var, value);
  	}
  }
  
  void P4BridgeServer::SetProtocol(const char *var, const char *value)
  {
  	__try
  	{
  		return P4BridgeServer::SetProtocol_Int(var, value);
  	}
  	__except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation()))
  	{
  	}
  }
  
  /*******************************************************************************
   *
   *  CallTextResultsCallbackFn
   *
   *  Simple wrapper to call the callback function (if it has been set) within a
   *      SEH __try block to catch any platform exception. SEH __try blocks must
   *      be contained in simple functions or you will get Compiler Error C2712,
   *      "cannot use __try in functions that require object unwinding"
   *
   ******************************************************************************/
  
  void P4BridgeServer::CallTextResultsCallbackFn(int cmdId, const char *data)
  {
  	string* pErrorString = NULL;
  	__try
  	{
  		if ((cmdId > 0) && (pTextResultsCallbackFn != NULL))
  		{
  			(*pTextResultsCallbackFn)( cmdId, data );
  		}
  	}  __except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  }
  
  /*******************************************************************************
   *
   *  CallInfoResultsCallbackFn
   *
   *  Simple wrapper to call the callback function (if it has been set) within a
   *      SEH __try block to catch any platform exception. SEH __try blocks must
   *      be contained in simple functions or you will get Compiler Error C2712,
   *      "cannot use __try in functions that require object unwinding"
   *
   ******************************************************************************/
  
  void P4BridgeServer::CallInfoResultsCallbackFn( int cmdId, int msgId, char level, const char *data )
  {
  	string* pErrorString = NULL;
  	__try
  	{
  		if 	((cmdId > 0) && (pInfoResultsCallbackFn != NULL))
  		{
  			int nlevel = (int)(level - '0');
  			(*pInfoResultsCallbackFn)( cmdId, msgId, nlevel, data );
  		}
  	}
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  }
  
  /*******************************************************************************
   *
   *  CallTaggedOutputCallbackFn
   *
   *  Simple wrapper to call the callback function (if it has been set) within a
   *      SEH __try block to catch any platform exception. SEH __try blocks must
   *      be contained in simple functions or you will get Compiler Error C2712,
   *      "cannot use __try in functions that require object unwinding"
   *
   ******************************************************************************/
  
  void P4BridgeServer::CallTaggedOutputCallbackFn( int cmdId, int objId, const char *pKey, const char * pVal )
  {
  	string* pErrorString = NULL;
  	__try
  	{
  		if ((cmdId > 0) && (pTaggedOutputCallbackFn != NULL))
  		{
  			(*pTaggedOutputCallbackFn)( cmdId, objId, pKey, pVal );
  		}
  	}
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection(cmdId)->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  }
  
  /*******************************************************************************
   *
   *  CallErrorCallbackFn
   *
   *  Simple wrapper to call the callback function (if it has been set) within a
   *      SEH __try block to catch any platform exception. SEH __try blocks must
   *      be contained in simple functions or you will get Compiler Error C2712,
   *      "cannot use __try in functions that require object unwinding"
   *
   ******************************************************************************/
  
  void P4BridgeServer::CallErrorCallbackFn( int cmdId, int severity, int errorId, const char * errMsg )
  {
  	string* pErrorString = NULL;
  	__try
  	{
  		if 	((cmdId > 0) && (pErrorCallbackFn != NULL))
  		{
  			(*pErrorCallbackFn)( cmdId, severity, errorId, errMsg );
  		}
  	}
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		// could cause infinite recursion if we keep producing errors 
  		//  when reporting errors
  		pErrorCallbackFn = NULL;
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  }
  /*******************************************************************************
   *
   *  CallErrorCallbackFn
   *
   *  Simple wrapper to call the callback function (if it has been set) within a
   *      SEH __try block to catch any platform exception. SEH __try blocks must
   *      be contained in simple functions or you will get Compiler Error C2712,
   *      "cannot use __try in functions that require object unwinding"
   *
   ******************************************************************************/
  
  void P4BridgeServer::CallBinaryResultsCallbackFn( int cmdId, void * data, int length )
  {
  	string* pErrorString = NULL;
  	__try
  	{
  		if ((cmdId > 0) && (pBinaryResultsCallbackFn))
  		{
  			(*pBinaryResultsCallbackFn)( cmdId, (void *) data, length );
  		}
  	}
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  }
  
  // Set the call back function to receive the tagged output
  void P4BridgeServer::SetTaggedOutputCallbackFn(IntTextTextCallbackFn* pNew)
  {
  	pTaggedOutputCallbackFn = pNew;
  }
  
  // Set the call back function to receive the error output
  void P4BridgeServer::SetErrorCallbackFn(IntIntIntTextCallbackFn* pNew)
  {
  	pErrorCallbackFn = pNew;
  }
  
  void P4BridgeServer::Prompt( int cmdId, const StrPtr &msg, StrBuf &rsp, 
  			int noEcho, Error *e )
  {
  	string* pErrorString = NULL;
  
  	__try
  	{
  		if ((cmdId > 0) && (pPromptCallbackFn))
  		{
  			char response[1024];
  
  			(*pPromptCallbackFn)( cmdId, msg.Text(), response, sizeof(response), noEcho);
  
  			rsp.Set(response);
  		}
  	}  __except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		
  	}
  }
  
  void P4BridgeServer::SetPromptCallbackFn( PromptCallbackFn * pNew)
  {
  	pPromptCallbackFn = pNew;
  }
  
  void P4BridgeServer::SetParallelTransferCallbackFn(ParallelTransferCallbackFn * pNew)
  {
  	pParallelTransferCallbackFn = pNew;
  }
  
  // Set the call back function to receive the information output
  void P4BridgeServer::SetInfoResultsCallbackFn(IntIntIntTextCallbackFn* pNew)
  {
  	pInfoResultsCallbackFn = pNew;
  }
  
  // Set the call back function to receive the text output
  void P4BridgeServer::SetTextResultsCallbackFn(TextCallbackFn* pNew)
  {
  	pTextResultsCallbackFn = pNew;
  }
  
  // Set the call back function to receive the binary output
  void P4BridgeServer::SetBinaryResultsCallbackFn(BinaryCallbackFn* pNew)
  {
  	pBinaryResultsCallbackFn = pNew;
  }
  
  // Callbacks for handling interactive resolve
  int	P4BridgeServer::Resolve( int cmdId, ClientMerge *m, Error *e )
  {
  	if (pResolveCallbackFn == NULL)
  	{
  		return CMS_SKIP;
  	}
  	P4ClientMerge *merger = new P4ClientMerge(m);
  	int result = -1;
  
  	result = Resolve_int( cmdId, merger );
  
  	delete merger;
  
  	if (result == -1)
  	{
  		LOG_LOC();
  		return getConnection()->getUi()->ClientUser::Resolve( m, e );
  	}
  	return result;
  }
  
  int	P4BridgeServer::Resolve( int cmdId, ClientResolveA *r, int preview, Error *e )
  {
  	if (pResolveACallbackFn == NULL)
  	{
  		return CMS_SKIP;
  	}
  
  	P4ClientResolve *resolver = new P4ClientResolve(r, isUnicode);
  	int result = -1;
  
  	result = Resolve_int( cmdId, resolver, preview, e);
  
  	delete resolver;
  
  	if (result == -1)
  	{
  		return CMS_SKIP;
  	}
  	return result;
  }
  
  void P4BridgeServer::SetResolveCallbackFn(ResolveCallbackFn * pNew)
  {
  	pResolveCallbackFn = pNew;
  }
  
  void P4BridgeServer::SetResolveACallbackFn(ResolveACallbackFn * pNew)
  {
  	pResolveACallbackFn = pNew;
  }
  
  int P4BridgeServer::Resolve_int( int cmdId, P4ClientMerge *merger)
  {
  	int result = -1;
  	string* pErrorString = NULL;
  	__try
  	{
  		if ((cmdId > 0) && (pResolveCallbackFn != NULL))
  		{
  			result = (*pResolveCallbackFn)(cmdId, merger);
  		}  
  	}
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  	return result;
  }
  
  int P4BridgeServer::Resolve_int( int cmdId, P4ClientResolve *resolver, int preview, Error *e)
  {
  	int result = -1;
  	string* pErrorString = NULL;
  
  	__try
  	{
  		if ((cmdId > 0) && (pResolveACallbackFn != NULL))
  		{
  			result = (*pResolveACallbackFn)(cmdId, resolver, preview);
  		}
  	}  
  	__except (HANDLE_EXCEPTION(&pErrorString))
  	{
  		LOG_LOC();
  		getConnection()->getUi()->HandleError( E_FATAL, 0, pErrorString->c_str() );
  		DELETE_OBJECT(pErrorString);
  	}
  	return result;
  }
  	
  int P4BridgeServer::IsIgnored_Int( const StrPtr &path )
  {
  	ClientApi client;
  	Error e;
  	client.SetCharset("utf8");
  	Ignore* ignore = client.GetIgnore();
  	const StrPtr ignoreFile = client.GetIgnoreFile();
  	if(!ignore)
  	{
  		return 0;
  	}
  
  	return ignore->Reject( path, ignoreFile );
  }	
  
  int P4BridgeServer::IsIgnored( const StrPtr &path )
  {
  	__try
  	{
  		return IsIgnored_Int(path);
  	}  
  	__except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation()))
  	{
  	}
  	return 0;
  }
  
  string P4BridgeServer::GetTicketFile()
  {
  	LOCK(&envLock);
      StrBuf ticketfile;
  	char* c;
  	HostEnv h;
  
  	// ticketfile - where users login tickets are stashed
      if( c = _enviro.Get( "P4TICKETS" ) )
  	{
  		ticketfile.Set( c );
  	}
  	else 
  	{
  		h.GetTicketFile( ticketfile, &_enviro );
  	}
  	return ticketfile.Text();
  }
  
  string P4BridgeServer::GetTicket(char* uri, char* user)
  {
  	LOCK(&envLock);
  	StrBuf ticketfile;
  	char* c;
  	HostEnv h;
  
  	// ticketfile - where users login tickets are stashed
      c = _enviro.Get( "P4TICKETS" );
  	if (c)
  	{
  		ticketfile.Set( c );
  	}
  	else 
  	{
  		h.GetTicketFile( ticketfile, &_enviro );
  	}
      Ticket t(&ticketfile);
      StrBuf port(uri);
      StrBuf userStr(user);
  
  	return t.GetTicket(port, userStr);
  }
  
  LogCallbackFn* P4BridgeServer::SetLogCallFn(LogCallbackFn *log_fn)
  {
  	LogCallbackFn* old = pLogFn;
  	pLogFn = log_fn;
  	return old;
  }
  
  int P4BridgeServer::DoTransferInternal(
  	const char *cmd,
  	std::vector<const char*> &argList,
  	StrDictListIterator *varDictIterator,
  	int threads,
  	Error *e)
  {
  	LOG_ENTRY();
  	__try
  	{
  		// TODO: Error* management, not clear if it's needed
  		return pParallelTransferCallbackFn((int*)this, cmd, argList.data(), (int) argList.size(), (int*) varDictIterator, threads);
  	}
  	__except (HANDLE_EXCEPTION_NOSTR())
  	{
  		LOG_ERROR("An exception occured handling a parallel operation");
  		return 1;
  	}
  }
  
  int P4BridgeServer::DoTransfer(
  	ClientApi *client,
  	ClientUser *ui,
  	const char *cmd,
  	StrArray &args,
  	StrDict &pVars,
  	int threads,
  	Error *e)
  {
  	LOG_ENTRY();
  	if (!pParallelTransferCallbackFn)
  		return 1;	// we're not prepared to handle this
  
  	// this function should not return until all of the threads have completed,
  	// so local (stack) transforms of P4 objects to .Net objects is OK
  	std::vector<const char*> argList;
  	for (int i = 0; i < args.Count(); i++)
  	{
  		argList.push_back(args.Get(i)->Value());
  	}
  
  	// copy the pVars to a local StrDictList for the iterator
  	StrDictList dList;
  	dList.Data()->CopyVars(pVars);
  	StrDictListIterator varDictIterator(&dList);
  
  	return DoTransferInternal(cmd, argList, &varDictIterator, threads, e);
  }
  
  // the transfer shim
  ParallelTransfer::ParallelTransfer(P4BridgeServer* pServer) : 
  	p4base(Type()),
  	pBridgeServer(pServer) 
  {
  }
  
  ParallelTransfer::~ParallelTransfer()
  {
  	LOG_LOC();
  }
  
  void ParallelTransfer::SetBridgeServer(P4BridgeServer* pServer) 
  {
  	pBridgeServer = pServer;
  }
  
  
  int ParallelTransfer::Transfer(ClientApi* client,
  	ClientUser *ui,
  	const char *cmd,
  	StrArray &args,
  	StrDict &pVars,
  	int threads,
  	Error *e)
  {
  	LOG_ENTRY();
  	// not bridge server to tell?  fail!
  	return (!pBridgeServer) ? 1 : pBridgeServer->DoTransfer(client, ui, cmd, args, pVars, threads, e);
  }