/******************************************************************************* 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 <strtable.h> //#include <strarray.h> #include <spec.h> #include <debug.h> #if __APPLE__ #include <langinfo.h> #endif #include <enviro.h> #include <ignore.h> #include <hostenv.h> #include "ticket.h" #define DELETE_OBJECT(obj) if( obj != NULL ) { delete obj; obj = NULL; } #define DELETE_ARRAY(obj) if( obj != NULL ) { delete[] obj; obj = 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; } // 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: 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( buff1, buffSize, message, args); buffSize *= 2; } va_end(args); int ret = 0; __try { ret = (*pLogFn)(log_level, file, line, buff1); } __except (HandleException_Static(GetExceptionCode(), GetExceptionInformation())) #ifdef _WIN32 { // bad ptr? pLogFn = NULL; } #endif 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) { } P4BridgeClient* P4BridgeServer::get_ui( int cmdId ) { P4ClientError *err = NULL; //if( !connected( &err ) ) //{ // return 0; //} if (!ConMgr) { return NULL; } P4Connection* con = ConMgr->GetConnection(cmdId); if (!con) { return NULL; } return con->ui; } P4BridgeClient* P4BridgeServer::find_ui( int cmdId ) { P4ClientError *err = NULL; //if( !connected( &err ) ) //{ // return 0; //} if (!ConMgr) { return NULL; } P4Connection* con = (P4Connection*) ConMgr->Find(cmdId); if (!con) { return NULL; } return con->ui; } /******************************************************************************* * * Constructer * * Create a P4BridgeServer and connect to the specified P4 Server. * ******************************************************************************/ P4BridgeServer::P4BridgeServer( const char *server, const char *user, const char *pass, const char *ws_client) : p4base(Type()) { //LOG_DEBUG3(4,"Creating a new P4BridgeServer ob %s for user, %s, and client, %s", server, 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; ExceptionError = NULL; pProgramName = NULL; pProgramVer = NULL; // connect to the server using a tagged protocol //client_tagged = new ClientApi; //if( server ) // client_tagged->SetPort( server ); //if( user ) // client_tagged->SetUser( user ); //if( pass ) // client_tagged->SetPassword( pass ); //if( ws_client ) // client_tagged->SetClient( ws_client ); //client_tagged->SetProtocol( "tag", "" ); //// return spec string when getting spec based objects //client_tagged->SetProtocol( "specstring", "" ); //client_tagged->SetProtocol( "api", "70" ); // 2006.1 api //client_tagged->SetProtocol( "enableStreams", "" ); // // Load the current P4CHARSET if set. // //if( client_tagged->GetCharset().Length() ) // char * cs = client_tagged->GetCharset().Text(); // connect to the server using a untagged protocol ConMgr = new ConnectionManager(this); if( server ) ConMgr->SetPort( server ); if( user ) ConMgr->SetUser( user ); if( pass ) ConMgr->SetPassword( pass ); if( ws_client ) ConMgr->SetClient( ws_client ); //client->SetProtocol( "specstring", "" ); //client->SetProtocol( "api", "72" ); // 2006.1 api //client->SetProtocol( "enableStreams", "" ); //if( server ) //{ // P4ClientError *err = NULL; // if (!connected(&err)) // { // return; // } // p4debug.SetLevel("-vnet.maxwait=5"); //} //if( client->GetCharset().Length() ) // char * cs = client->GetCharset().Text(); } /******************************************************************************* * * Destructor * * Close the connection and free up resources. * ******************************************************************************/ P4BridgeServer::~P4BridgeServer(void) { if (disposed != 0) { return; } else { LOCK(&Locker); disposed = 1; //if (enviro) //{ // delete enviro; // enviro = NULL; //} // Clear the the callbacks pTaggedOutputCallbackFn = NULL; pErrorCallbackFn = NULL; pInfoResultsCallbackFn = NULL; pTextResultsCallbackFn = NULL; pBinaryResultsCallbackFn = NULL; pPromptCallbackFn = NULL; pResolveCallbackFn = NULL; pResolveACallbackFn = NULL; close_connection(); if (ConMgr) { ConnectionManager* pOldConMgr = ConMgr; ConMgr = NULL; delete pOldConMgr; } //delete client_tagged; //client_tagged = NULL; DELETE_OBJECT(ExceptionError); DELETE_OBJECT(pProgramName); DELETE_OBJECT(pProgramName); } Locker.FreeCriticalSection(); } /******************************************************************************* * * connected * * Connect to the specified P4 Server, create a UI. * ******************************************************************************/ int P4BridgeServer::connected( P4ClientError **err ) { __try { return connected_int( err ); } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { } connecting = 0; return 0; } int P4BridgeServer::connected_int( P4ClientError **err ) { LOCK(&Locker); *err = NULL; if((connecting) || ( ConMgr && ConMgr->Initialized)) { return 1; // already connected } connecting = 1; if (!ConMgr) { // Create the p4client instance ConMgr = new ConnectionManager(this); } //char* arg = "-y"; //char** args = &arg; //run_command( "trust", 1, args, 1 ); //if (!run_command( "trust", 1, args, 1 )) //{ // P4ClientError *e = ui->GetErrorResults(); // if (e!= NULL) // { // *err = CopyStr(e->Message); // } // 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"); ConMgr->Initialized = 1; connecting = 0; return 1; } close_connection(); ConMgr->Initialized = 0; 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 (HandleException(GetExceptionCode(), GetExceptionInformation())) { } connecting = 0; return 0; } int P4BridgeServer::connect_and_trust_int( P4ClientError **err, char* trust_flag, char* fingerprint ) { LOCK(&Locker); if((connecting) || ( ConMgr && ConMgr->Initialized)) { return 1; // already connected } connecting = 1; if (!ConMgr) { // Create the p4client instance ConMgr = new ConnectionManager(this); } P4Connection* pCon = ConMgr->GetConnection(0); 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->ui->GetErrorResults(); if ((e!= NULL) && (e->Severity >= E_FAILED)) { *err = e; } connecting = 0; ConMgr->Initialized = 0; ConMgr->ReleaseConnection(pCon,0); 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"); ConMgr->Initialized = 1; connecting = 0; ConMgr->ReleaseConnection(pCon,0); return 1; } close_connection(); ConMgr->Initialized = 0; connecting = 0; return 0; } /******************************************************************************* * * close_connection * * Final disconnect from the P4 Server. * ******************************************************************************/ int P4BridgeServer::close_connection() { LOCK(&Locker); // Close connections if (ConMgr) { if (!ConMgr->Disconnect()) { return 0; } //delete ConMgr; //ConMgr = NULL; } //ConMgr= new ConnectionManager(this); // Set the Unicode flag to unknown, to force a retest isUnicode = -1; apiLevel = -1; useLogin = -1; supportsExtSubmit = -1; return 1; } /******************************************************************************* * * disconnect * * Disconnect from the P4 Server after a command, but save protocols and other * settings. * ******************************************************************************/ int P4BridgeServer::disconnect( void ) { LOCK(&Locker); if (ConMgr) { return ConMgr->Disconnect(); } return 1; } /******************************************************************************* * * get_charset * * Get the character set from the environment. * ******************************************************************************/ const StrPtr* P4BridgeServer::get_charset( ) { return ConMgr->GetCharset(); } CharSetApi::CharSet GetDefaultCharSet() { #ifdef _WIN32 switch (GetACP()) #elif __APPLE__ char* codeset = nl_langinfo(CODESET); int codesetInt = 0; sscanf(codeset, "CP%d", &codesetInt); switch (codesetInt) #endif { 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. * ******************************************************************************/ char * P4BridgeServer::set_charset( const char* c, const char * filec ) { static StrBuf m; CharSetApi::CharSet cs; if (c) { // Lookup the correct enum for the specified character set for the API cs = CharSetApi::Lookup( c ); if( cs < 0 ) { 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 ) { m = "Unknown or unsupported charset: "; m.Append( filec ); LOG_ERROR( m.Text() ); return m.Text(); } } else { // default value filecs = CharSetApi::WIN_US_ANSI; const StrPtr* filec = ConMgr->GetCharset(); if (filec) { filecs = CharSetApi::Lookup(filec->Text()); //if ((int)filecs <= 0) //{ // // see if there is a p4 config file, and it has a setting for p4cheaset // const StrPtr *cfgFile = get_config(); // if (cfgFile != NULL) // { // char* cfgFileName = cfgFile->Text(); // if ((cfgFileName != NULL) && (strncmp(cfgFileName,"noconfig", 8)!= 0)) // { // } // } //} if ((int)filecs <= 0) { // see if there is a p4 config file, and it has a setting for p4cheaset //see if there is asetting for p4 charset // not set, get a value based on the system code page filecs = GetDefaultCharSet(); } } } LOG_INFO1( "[P4] Setting charset: %s\n", CharSetApi::Name(filecs) ); // Set the character set in the untagged client manager ConMgr->SetCharset( cs, filecs ); // Tell the UI to use Unicode //UseUnicode(1); return NULL; } /******************************************************************************* * * set_cwd * * Set the working directory. * ******************************************************************************/ void P4BridgeServer::set_cwd( const char* newCwd ) { if (ConMgr) { ConMgr->SetCwd( newCwd ); } } /******************************************************************************* * * get_cwd * * Get the working directory. * ******************************************************************************/ static StrBuf EmptStr(""); const StrPtr& P4BridgeServer::get_cwd( void ) { if (ConMgr) { P4Connection *pCon = ConMgr->GetConnection(0); if (pCon) { ConMgr->ReleaseConnection(pCon,0); return pCon->GetCwd(); } } return EmptStr; } void P4BridgeServer::Run_int(P4Connection* client, const char *cmd, P4BridgeClient* ui) { __try { // LOG_DEBUG1(4, "Run_int: %s", cmd); client->Run(cmd, ui ); } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 if (ui) { ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); } #endif } } #ifndef _WIN32 typedef char CHAR; typedef CHAR * LPSTR; typedef unsigned long DWORD; typedef unsigned int UINT; typedef bool BOOL; typedef void * LPVOID; typedef unsigned short WORD; #define lstrcpy strcpy #define lstrcat strcat #define MAXPATH 260 #endif char *GetInfo(char* lpstrVffInfo, char *InfoItem) { #ifndef OS_NT return NULL; #else 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(szResult, "%04x%04x", (WORD)(*((DWORD *)lpVersion)), (WORD)(*((DWORD *)lpVersion)>>16)); // lstrcpy(szResult, lpVersion); } 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 (szGetName, "\\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; } return szResult; #endif } /******************************************************************************* * * 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 ) { //if (cmdId == 2) //{ // _asm int 3; //} //LOG_DEBUG1(4, "Run Command: %s", cmd); P4ClientError *err = NULL; if( connected( &err ) ) { DELETE_OBJECT(err) } Error e; StrBuf msg; P4Connection* client = ConMgr->GetConnection(cmdId); if(!client) { LOG_ERROR1( "Error getting connection for command: %d", cmdId ); return 0; } P4BridgeClient* ui = client->ui; if (ui) { if (err != NULL) { // couldn't connect ui->HandleError( err ); return 0; } ui->clear_results(); } else { ui = new P4BridgeClient(this, client); client->ui = ui; } client->IsAlive(1); // Connect to server //client->Init( &e ); if(client->Dropped()) { client->Final(&e); if( e.Test() ) { ui->HandleError(&e); return 0; } client->clientNeedsInit = 1; } if (client->clientNeedsInit) { client->Init( &e ); if( e.Test() ) { ui->HandleError(&e); return 0; } client->clientNeedsInit = 0; } // Label Connections for p4 monitor char* pModPath = NULL; if (!pProgramName) { #ifdef OS_NT pModPath = new char[MAX_PATH]; if ( GetModuleFileName( NULL, pModPath, MAX_PATH ) == 0 ) { delete[] pModPath ; } else { int idx1 = 0; int idx2 = 0; while ((idx1 < MAX_PATH) && (pModPath[idx1] != '\0')) { if (pModPath[idx1++] == '\\') { // interested in the character after the '\\' 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'; } } } if (pProgramName) client->SetProg( pProgramName ); else client->SetProg( "dot-net-api-p4" ); #else client->SetProg( "dot-net-api-p4" ); } #endif #ifdef OS_NT if (!pProgramVer && 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) { 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); int strSz = _scprintf("%d.%d.%d.%d", MajorVersion,MinorVersion,BuildNumber,RevisionNumber)+1; pProgramVer = new char[strSz+1]; _snprintf(pProgramVer, strSz, "%d.%d.%d.%d", MajorVersion,MinorVersion,BuildNumber,RevisionNumber); } } } char* prodName = GetInfo(lpData, "ProductName"); if (prodName) { DELETE_ARRAY(pProgramName); pProgramName = prodName; } 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 { DWORD err = GetLastError(); DWORD retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER| FORMAT_MESSAGE_FROM_SYSTEM| FORMAT_MESSAGE_ARGUMENT_ARRAY, NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&pProgramVer, 0, NULL ); } #endif } if (pProgramVer) client->SetVersion( pProgramVer ); else client->SetVersion( "NoVersionSpecified"); //Nobody liked "1.0" #else client->SetVersion( "NoVersionSpecified"); #endif client->SetVar(P4Tag::v_tag, tagged ? "yes" : 0); client->SetArgv( argc, args ); client->SetBreak(client); Run_int(client, cmd, ui); //ConMgr->ReleaseConnection(cmdId); P4ClientError* errors = ui->GetErrorResults(); // close the server connection //client->Final( &e ); //if( e.Test() ) //{ //e.Fmt( &msg, EF_NEWLINE ); //err = CopyStr( msg.Text() ); //LOG_ERROR1( "Error connecting to server: %s", err ); //ui->HandleError(&e); //} if (errors != NULL) { int maxSeverity = errors->MaxSeverity(); if ( maxSeverity >= 3 ) { return 0; } } if(client->Dropped()) { client->Final(&e); if( e.Test() ) { ui->HandleError(&e); } client->clientNeedsInit = 1; } return 1; } void P4BridgeServer::cancel_command(int cmdId) { if (ConMgr) { P4Connection* con = ConMgr->GetConnection(cmdId); if (con) { con->cancel_command(); } } } void P4BridgeServer::ReleaseConnection(int cmdId, P4UINT64 releaseTime) { if (ConMgr) { ConMgr->ReleaseConnection(cmdId, releaseTime); } } int P4BridgeServer::FreeConnections(P4UINT64 currentTime) { if (ConMgr) { return ConMgr->FreeConnections(currentTime); } return 0; } int P4BridgeServer::GetServerProtocols(P4ClientError **err) { if (isUnicode >= 0) { // already read the protocols return 1; } // 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 = ConMgr->GetConnection(0); if (!run_command( "help", 0, 1, NULL, 0 )) { bool ok = false; *err = ConMgr->GetConnection(0)->ui->GetErrorResults(); // clear the erro pointer or it will get deleted when/if the connection is // closed du to the error ConMgr->GetConnection(0)->ui->ClearErrorResults(); P4ClientError* error = *err; while (error) { //int tc = ErrorOf(13, 334, 0, 0, 0); if (error->ErrorCode == ErrorOf(13, 334, 0, 0, 0)) { ok = true; *err = NULL; break; } error= error->Next; } if (!ok) { isUnicode = -1; ConMgr->ReleaseConnection(0,0); return 0; } } StrPtr *st = 0; if ( st = pCon->GetProtocol( P4Tag::v_unicode ) ) { if( st->Length() && st->Atoi() ) { isUnicode = 1; } else { isUnicode = 0; } } // Check server level st = pCon->GetProtocol( "server2" ); if ( st != NULL ) { apiLevel = st->Atoi(); if (apiLevel == 0) { // this failed isUnicode = -1; useLogin = -1; supportsExtSubmit = -1; ConMgr->ReleaseConnection(0,0); return 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; } } ConMgr->ReleaseConnection(pCon,0); 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() { 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) close_connection(); if (newPort) { ConMgr->SetPort( newPort ); } if (newUser) { ConMgr->SetUser( newUser ); } if (newUser) { ConMgr->SetPassword( newUser ); } if (newClient) { ConMgr->SetClient( newClient ); } } /******************************************************************************* * * set_client * * Set the workspace used for the connection. * ******************************************************************************/ void P4BridgeServer::set_client( const char* newVal ) { if (ConMgr) { // close the connection to force reconnection with new value(s) close_connection(); ConMgr->SetClient( newVal ); //client_tagged->SetClient( newVal ); } } /******************************************************************************* * * set_user * * Set the user name used for the connection. * ******************************************************************************/ void P4BridgeServer::set_user( const char* newVal ) { if (ConMgr) { // close the connection to force reconnection with new value(s) close_connection(); ConMgr->SetUser( newVal ); //client_tagged->SetUser( newVal ); } } /******************************************************************************* * * set_port * * Set the port (hostname:portnumber) used for the connection. * ******************************************************************************/ void P4BridgeServer::set_port( const char* newVal ) { if (ConMgr) { // close the connection to force reconnection with new value(s) close_connection(); ConMgr->SetPort( newVal); //client_tagged->SetPort( newVal ); } } /******************************************************************************* * * set_password * * Set the password used for the connection. * ******************************************************************************/ void P4BridgeServer::set_password( const char* newVal ) { if (ConMgr) { // close the connection to force reconnection with new value(s) close_connection(); ConMgr->SetPassword( newVal ); //client_tagged->SetPassword( newVal ); } } /******************************************************************************* * * set_programName * * Set the program name used for the connection. * ******************************************************************************/ void P4BridgeServer::set_programName( const char* newVal ) { if (pProgramName) delete[] pProgramName; pProgramName = CopyStr(newVal); } /******************************************************************************* * * set_programVer * * Set the program version used for the connection. * ******************************************************************************/ void P4BridgeServer::set_programVer( const char* newVal ) { if (pProgramVer) delete pProgramVer; pProgramVer = CopyStr(newVal); } /******************************************************************************* * * get_client * * Get the workspace used for the connection. * ******************************************************************************/ const StrPtr* P4BridgeServer::get_client() { if (ConMgr) { P4Connection* pCon = ConMgr->GetConnection(0); const StrPtr* ret = &(pCon->GetClient()); ConMgr->ReleaseConnection(pCon,0); return ret; } return NULL; } /******************************************************************************* * * get_user * * Get the user name used for the connection. * ******************************************************************************/ const StrPtr* P4BridgeServer::get_user() { if (ConMgr) { P4Connection* pCon = ConMgr->GetConnection(0); const StrPtr* ret = &(pCon->GetUser()); ConMgr->ReleaseConnection(pCon,0); return ret; } return NULL; } /******************************************************************************* * * get_port * * Get the user port used for the connection. * ******************************************************************************/ const StrPtr* P4BridgeServer::get_port() { if (ConMgr) { P4Connection* pCon = ConMgr->GetConnection(0); const StrPtr* ret = &(pCon->GetPort()); ConMgr->ReleaseConnection(pCon,0); return ret; } return NULL; } /******************************************************************************* * * get_password * * Get the password used for the connection. * ******************************************************************************/ const StrPtr* P4BridgeServer::get_password() { if (ConMgr) { P4Connection* pCon = ConMgr->GetConnection(0); const StrPtr* ret = &(pCon->GetPassword()); ConMgr->ReleaseConnection(pCon,0); return ret; } return NULL; } /******************************************************************************* * * get_programName * * Get the program name used for the connection. * ******************************************************************************/ const char* P4BridgeServer::get_programName() { return pProgramName; } /******************************************************************************* * * get_programVer * * Get the program version used for the connection. * ******************************************************************************/ const char* P4BridgeServer::get_programVer() { return pProgramVer; } /******************************************************************************* * * get_config * * Get the config file used for the connection, if any. * ******************************************************************************/ char* P4BridgeServer::get_config_Int(const char * cwd) { Enviro enviro; enviro.SetCharSet(1); // set utf 8 StrRef sCwd(cwd); enviro.Config( sCwd ); const StrPtr& p = enviro.GetConfig(); char *copy = CopyStr( p.Text() ); return copy; //return p.Text(); } char* P4BridgeServer::get_config(const char * cwd) { __try { return P4BridgeServer::get_config_Int(cwd); } __except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation())) { return NULL; } } /******************************************************************************* * * get_config * * Get the config file used for the connection, if any. * ******************************************************************************/ char* P4BridgeServer::get_config_Int() { P4ClientError *err = NULL; if( !connected( &err ) ) { return 0; } if (ConMgr) { P4Connection* pCon = ConMgr->GetConnection(0); const StrPtr& ret = pCon->GetConfig(); ConMgr->ReleaseConnection(pCon,0); char *copy = CopyStr( ret.Text() ); return copy; } return NULL; } char* P4BridgeServer::get_config() { __try { return P4BridgeServer::get_config_Int(); } __except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation())) { return NULL; } } /******************************************************************************* * * 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. * ******************************************************************************/ static const char* ExceptionSource = NULL; void SetExceptionSource(const char* value) { // using pointers to string literals so don't need to free the memory //DELETE_OBJECT(ExceptionSource); ExceptionSource = value; } int P4BridgeServer::HandleException(unsigned int c, struct _EXCEPTION_POINTERS *e) { #ifdef _WIN32 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; //case EXCEPTION_POSSIBLE_DEADLOCK: // exType = "EXCEPTION_POSSIBLE_DEADLOCK\r\n"); // break; default: printf("UNKNOWN EXCEPTION\r\n"); break; } DELETE_OBJECT(ExceptionError); ExceptionError = new StrBuf(); if (ExceptionSource == NULL) { ExceptionError->Append("Exception Detected in callback function: "); } else { ExceptionError->Append("Exception Detected in function "); ExceptionError->Append(ExceptionSource); ExceptionError->Append(": "); } ExceptionError->Append(exType); ExceptionError->Terminate(); LOG_ERROR(ExceptionError->Text()); } #endif return EXCEPTION_EXECUTE_HANDLER; } int P4BridgeServer::sHandleException(unsigned int c, struct _EXCEPTION_POINTERS *e) { return EXCEPTION_EXECUTE_HANDLER; } char* P4BridgeServer::Get_Int( const char *var ) { Enviro enviro; enviro.SetCharSet(1); // set utf 8 char* copy = CopyStr(enviro.Get( var )); return copy; //return enviro.Get( var ); } char* P4BridgeServer::Get( const char *var ) { __try { return P4BridgeServer::Get_Int( var ); } __except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation())) { return NULL; } } void P4BridgeServer::Set_Int( const char *var, const char *value ) { Enviro enviro; enviro.SetCharSet(1); // set utf 8 Error e; enviro.Set( var, value, &e );; 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())) { } } /******************************************************************************* * * 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) { __try { if ((cmdId > 0) && (pTextResultsCallbackFn != NULL)) { SetExceptionSource("CallTextResultsCallbackFn"); (*pTextResultsCallbackFn)( cmdId, data ); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } } /******************************************************************************* * * 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 ) { __try { if ((cmdId > 0) && (pInfoResultsCallbackFn != NULL)) { SetExceptionSource("CallInfoResultsCallbackFn"); int nlevel = (int)(level - '0'); (*pInfoResultsCallbackFn)( cmdId, msgId, nlevel, data ); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } } /******************************************************************************* * * 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 ) { __try { if ((cmdId > 0) && (pTaggedOutputCallbackFn != NULL)) { SetExceptionSource("CallTaggedOutputCallbackFn"); (*pTaggedOutputCallbackFn)( cmdId, objId, pKey, pVal ); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } } /******************************************************************************* * * 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 ) { __try { if ((cmdId > 0) && (pErrorCallbackFn != NULL)) { SetExceptionSource("CallErrorCallbackFn"); (*pErrorCallbackFn)( cmdId, severity, errorId, errMsg ); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) #ifdef _WIN32 { // could cause infinite recursion if we keep producing errors // when reporting errors pErrorCallbackFn = NULL; ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); } #endif } /******************************************************************************* * * 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 ) { __try { if ((cmdId > 0) && (pBinaryResultsCallbackFn)) { SetExceptionSource("CallBinaryResultsCallbackFn"); (*pBinaryResultsCallbackFn)( cmdId, (void *) data, length ); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } } // 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 ) { __try { if ((cmdId > 0) && (pPromptCallbackFn)) { SetExceptionSource("CallBinaryResultsCallbackFn"); char response[1024]; (*pPromptCallbackFn)( cmdId, msg.Text(), response, sizeof(response), noEcho); rsp.Set(response); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } } void P4BridgeServer::SetPromptCallbackFn( PromptCallbackFn * pNew) { pPromptCallbackFn = 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) { return ConMgr->GetConnection(cmdId)->ui->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; __try { if ((cmdId > 0) && (pResolveCallbackFn != NULL)) { SetExceptionSource("Resolve_int1"); result = (*pResolveCallbackFn)(cmdId, merger); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } return result; } int P4BridgeServer::Resolve_int( int cmdId, P4ClientResolve *resolver, int preview, Error *e) { int result = -1; __try { if ((cmdId > 0) && (pResolveACallbackFn != NULL)) { SetExceptionSource("Resolve_int2"); result = (*pResolveACallbackFn)(cmdId, resolver, preview); } } __except (HandleException(GetExceptionCode(), GetExceptionInformation())) { #ifdef _WIN32 ConMgr->GetConnection(cmdId)->ui->HandleError( E_FATAL, 0, ExceptionError->Text()); DELETE_OBJECT(ExceptionError); #endif } return result; } int P4BridgeServer::IsIgnored_Int( const StrPtr &path ) { ClientApi client; Error e; client.SetCharset("utf8"); //client.Init(&e); Ignore* ignore = client.GetIgnore(); const StrPtr ignoreFile = client.GetIgnoreFile(); if(!ignore) { //LOG_DEBUG(4, "No IgnoreFile returned"); return 0; } int rv = ignore->Reject(path, ignoreFile); //LOG_DEBUG3(4, "Ignore?: %s against %s returns %d", path.Text(), ignoreFile.Text(), rv); //LOG_DEBUG1(4, "cwd: %s", client.GetCwd().Text()); return rv; } int P4BridgeServer::IsIgnored( const StrPtr &path ) { __try { return IsIgnored_Int(path); } __except (P4BridgeServer::sHandleException(GetExceptionCode(), GetExceptionInformation())) { } return 0; } char* P4BridgeServer::GetTicketFile() { StrBuf ticketfile; Enviro env = Enviro(); char* c; HostEnv h; // ticketfile - where users login tickets are stashed if( c = env.Get( "P4TICKETS" ) ) { ticketfile.Set( c ); } else { h.GetTicketFile( ticketfile, &env ); } char *copy = CopyStr( ticketfile.Text() ); return copy; //return ticketfile.Text(); } char* P4BridgeServer::GetTicket(char* uri, char* user) { StrBuf ticketfile; Enviro env = Enviro(); char* c; HostEnv h; // ticketfile - where users login tickets are stashed if( c = env.Get( "P4TICKETS" ) ) { ticketfile.Set( c ); } else { h.GetTicketFile( ticketfile, &env ); } Ticket t(&ticketfile); StrBuf port(uri); StrBuf userStr(user); char *copy = CopyStr( t.GetTicket(port, userStr) ); return copy; //return t.GetTicket(port, userStr); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 13953 | zynthar |
Populate -o //guest/perforce_software/p4connect/... //guest/zynthar/dev/p4connect/.... |
||
//guest/perforce_software/p4connect/src/P4Bridge/p4bridge/P4BridgeServer.cpp | |||||
#3 | 13942 | Norman Morse |
Fix Charset support for Assembla and unicode servers. Fix bug in Bridge where error messages were being deallocated before displayed Fixed P4Server to check the environment for P4CHARSET before trying to call "p4 set P4CHARSET". |
||
#2 | 12135 | Norman Morse |
Integrate dev branch changes into main. This code is the basiis of the 2.7 BETA release which provides Unity 5 compatibility |
||
#1 | 10940 | Norman Morse |
Inital Workshop release of P4Connect. Released under BSD-2 license |