You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2556 lines
72 KiB
2556 lines
72 KiB
//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
// prevent Error function from being defined, since it inteferes with the P4 API |
|
#define Error Warning |
|
#include "p4lib/ip4.h" |
|
#include "tier1/utlvector.h" |
|
#include "tier1/utlsymbol.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "tier1/utlstring.h" |
|
#include "tier1/utlmap.h" |
|
#include "tier1/characterset.h" |
|
|
|
#include "filesystem.h" |
|
#undef Error |
|
|
|
#undef Verify |
|
#include "clientapi.h" |
|
|
|
#include <time.h> |
|
#include <ctype.h> |
|
#include <io.h> |
|
#include <sys/stat.h> |
|
|
|
#include <windows.h> |
|
|
|
#define CLIENTSPEC_BUFFER_SIZE (16 * 1024) |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Stores info about the client path |
|
//----------------------------------------------------------------------------- |
|
struct CClientPathRecord |
|
{ |
|
char m_szDepotPath[MAX_PATH]; |
|
char m_szClientPath[MAX_PATH]; |
|
bool m_bNegative; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Global interfaces |
|
//----------------------------------------------------------------------------- |
|
#if defined(STANDALONE_VPC) |
|
#include "../utils/vpc/sys_utils.h" |
|
#define FileExists( arg ) Sys_Exists( arg ) |
|
#define ReadFile( path, unused, buf ) Sys_LoadFileIntoBuffer( path, buf, false ) |
|
#else |
|
static IFileSystem *g_pFileSystem; |
|
#define FileExists( arg ) g_pFileSystem->FileExists( arg ) |
|
#define ReadFile( path, unused, buf ) g_pFilesystem->ReadFile( path, unused, buf ) |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Interface to accessing P4 commands |
|
//----------------------------------------------------------------------------- |
|
class CP4 : public CBaseAppSystem<IP4> |
|
{ |
|
public: |
|
// Destructor |
|
virtual ~CP4(); |
|
|
|
// Methods of IAppSystem |
|
virtual bool Connect( CreateInterfaceFn factory ); |
|
virtual InitReturnVal_t Init(); |
|
virtual void *QueryInterface( const char *pInterfaceName ); |
|
virtual void Shutdown(); |
|
virtual void Disconnect(); |
|
|
|
// Inherited from IP4 |
|
P4Client_t &GetActiveClient(); |
|
virtual void RefreshActiveClient(); |
|
virtual void SetActiveClient(const char *clientname); |
|
virtual void GetDepotFilePath(char *depotFilePath, const char *filespec, int size); |
|
virtual void GetClientFilePath(char *clientFilePath, const char *filespec, int size); |
|
virtual void GetLocalFilePath(char *localFilePath, const char *filespec, int size); |
|
virtual CUtlVector<P4File_t> &GetFileList(const char *path); |
|
virtual CUtlVector<P4File_t> &GetFileListUsingClientSpec( const char *pPath, const char *pClientSpec ); |
|
virtual void GetOpenedFileList( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly ); |
|
virtual void GetOpenedFileList( const char *pRootDirectory, CUtlVector<P4File_t> &fileList ); |
|
virtual void GetOpenedFileListInPath( const char *pPathID, CUtlVector<P4File_t> &fileList ); |
|
virtual void GetFileListInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList ); |
|
|
|
virtual CUtlVector<P4Revision_t> &GetRevisionList(const char *path, bool bIsDir); |
|
virtual CUtlVector<P4Client_t> &GetClientList(); |
|
virtual void RemovePathFromActiveClientspec(const char *path); |
|
virtual void SetOpenFileChangeList( const char *pChangeListName ); |
|
virtual bool OpenFileForAdd( const char *fullpath ); |
|
virtual bool OpenFileForEdit(const char *fullpath); |
|
virtual bool OpenFileForDelete(const char *fullpath); |
|
virtual bool SyncFile( const char *pFullPath, int nRevision = -1 ); |
|
virtual bool SubmitFile( const char *pFullPath, const char *pDescription ); |
|
virtual bool RevertFile( const char *pFullPath ); |
|
virtual bool OpenFilesForAdd( int nCount, const char **ppFullPathList ); |
|
virtual bool OpenFilesForEdit( int nCount, const char **ppFullPathList ); |
|
virtual bool OpenFilesForDelete( int nCount, const char **ppFullPathList ); |
|
virtual bool SubmitFiles( int nCount, const char **ppFullPathList, const char *pDescription ); |
|
virtual bool RevertFiles( int nCount, const char **ppFullPathList ); |
|
virtual bool IsFileInPerforce( const char *fullpath ); |
|
virtual P4FileState_t GetFileState( const char *pFullPath ); |
|
virtual bool GetFileInfo( const char *pFullPath, P4File_t *pFileInfo ); |
|
|
|
virtual const char *GetDepotRoot(); |
|
virtual int GetDepotRootLength(); |
|
virtual const char *GetLocalRoot(); |
|
virtual int GetLocalRootLength(); |
|
virtual const char *String( CUtlSymbol s ) const; |
|
virtual bool GetClientSpecForFile( const char *pFullPath, char *pClientSpec, int nMaxLen ); |
|
virtual bool GetClientSpecForDirectory( const char *pFullPathDir, char *pClientSpec, int nMaxLen ); |
|
virtual bool GetClientSpecForPath( const char *pPathId, char *pClientSpec, int nMaxLen ); |
|
virtual void OpenFileInP4Win( const char *pFullPath ); |
|
virtual bool IsConnectedToServer( bool bRetry = true ); |
|
virtual const char *GetLastError(); |
|
char const * GetOpenFileChangeListNum(); // Returns NULL for default |
|
|
|
// Convert a depot file to a local file user the current client mapping |
|
bool DepotFileToLocalFile( const char *pDepotFile, char *pLocalFile, int nBufLen ); |
|
|
|
// Accessors |
|
ClientApi &GetClientApi() { return m_Client; } |
|
ClientUser &GetClientUser() { return m_User; } |
|
|
|
private: |
|
// Helper for operations on multiple files |
|
typedef void (*PerforceOp_t)( int nCount, const char **ppFullPathList, const char *pDescription ); |
|
bool PerformPerforceOp( PerforceOp_t op, int nCount, const char **ppFullPathList, const char *pDescription ); |
|
bool PerformPerforceOp( const char *pOperation, int nCount, const char **ppFullPathList ); |
|
bool PerformPerforceOp( const char *pOperation, const char *pFullPath ); |
|
|
|
bool PerformPerforceOpCurChangeList( const char *pOperation, int nCount, const char **ppFullPathList ); |
|
bool PerformPerforceOpCurChangeList( const char *pOperation, const char *pFullPath ); |
|
|
|
void RefreshClientData(); |
|
|
|
bool m_bConnectedToServer; |
|
P4Client_t m_ActiveClient; |
|
CUtlVector< CClientPathRecord > m_ClientMapping; |
|
char m_szDepotRoot[_MAX_PATH]; |
|
char m_szLocalRoot[_MAX_PATH]; |
|
int m_iDepotRootLength; |
|
int m_iLocalRootLength; |
|
CUtlString m_sChangeListName, m_sCachedChangeListNum; |
|
int m_nCachedChangeListNumber; |
|
|
|
// internal |
|
ClientApi m_Client; |
|
ClientUser m_User; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// global instance |
|
//----------------------------------------------------------------------------- |
|
CP4 s_p4; |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CP4, IP4, P4_INTERFACE_VERSION, s_p4 ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Scoped class to push and pop a particular client spec |
|
//----------------------------------------------------------------------------- |
|
class CScopedClientSpec |
|
{ |
|
public: |
|
CScopedClientSpec( const char *pClientSpec ); |
|
~CScopedClientSpec(); |
|
|
|
private: |
|
char m_pOldClientSpec[MAX_PATH]; |
|
bool m_bClientSpecNeedsRestore; |
|
}; |
|
|
|
CScopedClientSpec::CScopedClientSpec( const char *pClientSpec ) |
|
{ |
|
m_bClientSpecNeedsRestore = false; |
|
V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) ); |
|
if ( V_stricmp( m_pOldClientSpec, pClientSpec ) ) |
|
{ |
|
s_p4.SetActiveClient( pClientSpec ); |
|
m_bClientSpecNeedsRestore = true; |
|
} |
|
} |
|
|
|
CScopedClientSpec::~CScopedClientSpec() |
|
{ |
|
if ( m_bClientSpecNeedsRestore ) |
|
{ |
|
s_p4.SetActiveClient( m_pOldClientSpec ); |
|
} |
|
} |
|
|
|
|
|
class CScopedFileClientSpec |
|
{ |
|
public: |
|
CScopedFileClientSpec( const char *pFullPath ); |
|
~CScopedFileClientSpec(); |
|
|
|
private: |
|
char m_pOldClientSpec[MAX_PATH]; |
|
bool m_bClientSpecNeedsRestore; |
|
}; |
|
|
|
CScopedFileClientSpec::CScopedFileClientSpec( const char *pFullPath ) |
|
{ |
|
m_bClientSpecNeedsRestore = false; |
|
char pClientSpec[MAX_PATH]; |
|
if ( s_p4.GetClientSpecForFile( pFullPath, pClientSpec, sizeof(pClientSpec) ) ) |
|
{ |
|
V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) ); |
|
if ( V_stricmp( m_pOldClientSpec, pClientSpec ) ) |
|
{ |
|
s_p4.SetActiveClient( pClientSpec ); |
|
m_bClientSpecNeedsRestore = true; |
|
} |
|
} |
|
} |
|
|
|
CScopedFileClientSpec::~CScopedFileClientSpec() |
|
{ |
|
if ( m_bClientSpecNeedsRestore ) |
|
{ |
|
s_p4.SetActiveClient( m_pOldClientSpec ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Scoped class to push and pop a particular client spec |
|
//----------------------------------------------------------------------------- |
|
class CScopedDirClientSpec |
|
{ |
|
public: |
|
CScopedDirClientSpec( const char *pFullPathDir ); |
|
~CScopedDirClientSpec(); |
|
|
|
private: |
|
char m_pOldClientSpec[MAX_PATH]; |
|
bool m_bClientSpecNeedsRestore; |
|
}; |
|
|
|
CScopedDirClientSpec::CScopedDirClientSpec( const char *pFullPathDir ) |
|
{ |
|
m_bClientSpecNeedsRestore = false; |
|
char pClientSpec[MAX_PATH]; |
|
if ( s_p4.GetClientSpecForDirectory( pFullPathDir, pClientSpec, sizeof(pClientSpec) ) ) |
|
{ |
|
V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) ); |
|
if ( V_stricmp( m_pOldClientSpec, pClientSpec ) ) |
|
{ |
|
s_p4.SetActiveClient( pClientSpec ); |
|
m_bClientSpecNeedsRestore = true; |
|
} |
|
} |
|
} |
|
|
|
CScopedDirClientSpec::~CScopedDirClientSpec() |
|
{ |
|
if ( m_bClientSpecNeedsRestore ) |
|
{ |
|
s_p4.SetActiveClient( m_pOldClientSpec ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Scoped class to push and pop a particular client spec |
|
//----------------------------------------------------------------------------- |
|
class CScopedPathClientSpec |
|
{ |
|
public: |
|
CScopedPathClientSpec( const char *pPathID ); |
|
~CScopedPathClientSpec(); |
|
|
|
private: |
|
char m_pOldClientSpec[MAX_PATH]; |
|
bool m_bClientSpecNeedsRestore; |
|
}; |
|
|
|
CScopedPathClientSpec::CScopedPathClientSpec( const char *pPathID ) |
|
{ |
|
m_bClientSpecNeedsRestore = false; |
|
char pClientSpec[MAX_PATH]; |
|
if ( s_p4.GetClientSpecForPath( pPathID, pClientSpec, sizeof(pClientSpec) ) ) |
|
{ |
|
V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) ); |
|
s_p4.SetActiveClient( pClientSpec ); |
|
m_bClientSpecNeedsRestore = true; |
|
} |
|
} |
|
|
|
CScopedPathClientSpec::~CScopedPathClientSpec() |
|
{ |
|
if ( m_bClientSpecNeedsRestore ) |
|
{ |
|
s_p4.SetActiveClient( m_pOldClientSpec ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: utility function to split a typical P4 line output into var and value |
|
//----------------------------------------------------------------------------- |
|
static void SplitP4Output(const char *data, char *pszCmd, char *pszInfo, int bufLen) |
|
{ |
|
V_strncpy(pszCmd, data, bufLen); |
|
|
|
char *mid = V_strstr(pszCmd, " "); |
|
if (mid) |
|
{ |
|
*mid = 0; |
|
V_strncpy(pszInfo, data + (mid - pszCmd) + 1, bufLen); |
|
} |
|
else |
|
{ |
|
pszInfo[0] = 0; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: base class for parse input from the P4 server |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
class CDataRetrievalUser : public ClientUser |
|
{ |
|
public: |
|
CUtlVector<T> &GetData() |
|
{ |
|
return m_Data; |
|
} |
|
|
|
// call this to start retrieving data |
|
void InitRetrievingData() |
|
{ |
|
m_bAwaitingNewRecord = true; |
|
m_pData = &m_Data; |
|
m_pData->RemoveAll(); |
|
} |
|
|
|
// call this to start retrieving data into an external piece of memory |
|
void InitRetrievingData( CUtlVector<T> *pData ) |
|
{ |
|
m_bAwaitingNewRecord = true; |
|
m_pData = pData; |
|
m_pData->RemoveAll(); |
|
} |
|
|
|
T *ForceNextRecord() |
|
{ |
|
int index = m_pData->AddToTail(); |
|
if ( m_pData->Count() > 1 ) |
|
{ |
|
m_pData->Element(index) = m_pData->Element(0); |
|
} |
|
return &m_pData->Element(index); |
|
} |
|
// implement this to parse out input from the server into the specified object |
|
virtual void OutputRecord(T &obj, const char *pszVar, const char *pszInfo) = 0; |
|
|
|
|
|
private: |
|
bool m_bAwaitingNewRecord; |
|
CUtlVector<T> m_Data; |
|
CUtlVector<T> *m_pData; |
|
|
|
virtual void OutputInfo( char level, const char *data ) |
|
{ |
|
if ( V_strlen(data) < 1 ) |
|
{ |
|
// end of a record, await the new one |
|
m_bAwaitingNewRecord = true; |
|
return; |
|
} |
|
|
|
if ( m_bAwaitingNewRecord ) |
|
{ |
|
// add in the new record |
|
T &record = m_pData->Element( m_pData->AddToTail() ); |
|
memset( &record, 0, sizeof( record ) ); |
|
Construct( &record ); |
|
m_bAwaitingNewRecord = false; |
|
} |
|
|
|
// parse |
|
char szVar[_MAX_PATH]; |
|
char szInfo[_MAX_PATH]; |
|
SplitP4Output( data, szVar, szInfo, sizeof( szVar ) ); |
|
|
|
// emit |
|
T &record = m_pData->Element( m_pData->Count() - 1 ); |
|
OutputRecord( record, szVar, szInfo ); |
|
} |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retrieves a file list |
|
//----------------------------------------------------------------------------- |
|
class CFileUser : public CDataRetrievalUser<P4File_t> |
|
{ |
|
public: |
|
void RetrieveDir(const char *dir) |
|
{ |
|
// clear the list |
|
InitRetrievingData(); |
|
|
|
// search for the paths - this string gets all revisions of a file, |
|
// from the version they have locally to the current version |
|
// this is so we can see if the local files are out of date |
|
char szSearch[MAX_PATH]; |
|
V_snprintf(szSearch, sizeof(szSearch), "%s/*", dir); |
|
|
|
// gets directories first |
|
char *argv[] = { "-C", (char *)szSearch, NULL }; |
|
s_p4.GetClientApi().SetArgv( 2, argv ); |
|
s_p4.GetClientApi().Run("dirs", this); |
|
|
|
// now get files, '-Rc' restricts files to client mapping |
|
char *argv2[] = { "-Rc", "-Op", (char *)szSearch, NULL }; |
|
s_p4.GetClientApi().SetArgv( 3, argv2 ); |
|
s_p4.GetClientApi().Run("fstat", this); |
|
|
|
ComputeLocalDirectoryNames( GetData() ); |
|
} |
|
|
|
void RetrieveFile(const char *filespec) |
|
{ |
|
// clear the list |
|
InitRetrievingData(); |
|
|
|
// now get files, '-Rc' restricts files to client mapping |
|
char *argv2[] = { "-Rc", "-Op", (char *)filespec, NULL }; |
|
s_p4.GetClientApi().SetArgv( 3, argv2 ); |
|
s_p4.GetClientApi().Run("fstat", this); |
|
ComputeLocalDirectoryNames( GetData() ); |
|
} |
|
// Show how file names map through the client view |
|
// For each file in filespec, three names are produced: |
|
// depotFile the name in the depot |
|
// clientFile the name on the client in Perforce syntax |
|
// localFile the name on the client in local syntax |
|
void RetrieveWhereabouts(const char *filespec) |
|
{ |
|
// clear the list |
|
InitRetrievingData(); |
|
|
|
// figure out where filespec is |
|
char *argv[] = { (char *)filespec, NULL }; |
|
s_p4.GetClientApi().SetArgv( 1, argv ); |
|
s_p4.GetClientApi().Run("where", this); |
|
} |
|
|
|
void RetrieveOpenedFiles( const char *filespec ) |
|
{ |
|
// clear the list |
|
InitRetrievingData(); |
|
char *argv[] = { (char *)filespec, NULL }; |
|
s_p4.GetClientApi().SetArgv( 1, argv ); |
|
s_p4.GetClientApi().Run("opened", this); |
|
ComputeLocalFileNames( GetData() ); |
|
} |
|
|
|
void BeginChangelistProcess() |
|
{ |
|
m_bChangesRecord = true; |
|
m_nChangesIndex = 0; |
|
} |
|
void EndChangelistProcess() |
|
{ |
|
m_bChangesRecord = false; |
|
m_nChangesIndex = -1; |
|
} |
|
|
|
void RetrieveOpenedFiles( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly ) |
|
{ |
|
// clear the list |
|
InitRetrievingData( &fileList ); |
|
if ( bDefaultChangeOnly ) |
|
{ |
|
char *argv[] = { (char *)"-c", (char*)"default", NULL }; |
|
s_p4.GetClientApi().SetArgv( 2, argv ); |
|
} |
|
s_p4.GetClientApi().Run( "opened", this ); |
|
ComputeLocalFileNames( fileList ); |
|
} |
|
|
|
void RetrieveFilesInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList ) |
|
{ |
|
BeginChangelistProcess(); |
|
InitRetrievingData( &fileList ); |
|
char changeListString[32]; |
|
V_snprintf( changeListString, sizeof(changeListString), "%d", changeListNumber ); |
|
char *argv[] = { (char *)"-s", changeListString, NULL }; |
|
s_p4.GetClientApi().SetArgv( 2, argv ); |
|
s_p4.GetClientApi().Run( "describe", this ); |
|
EndChangelistProcess(); |
|
} |
|
|
|
|
|
private: |
|
void ComputeLocalFileNames( CUtlVector<P4File_t> &fileList ) |
|
{ |
|
// Need to construct valid local paths since opened doesn't do it for us |
|
char pMatchPattern[MAX_PATH]; |
|
V_snprintf( pMatchPattern, sizeof(pMatchPattern), "//%s/", s_p4.GetClientApi().GetClient().Text() ); |
|
int nLen = V_strlen( pMatchPattern ); |
|
|
|
int nCount = fileList.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( fileList[i].m_bDir ) |
|
continue; |
|
|
|
const char *pClientPath = s_p4.String( fileList[i].m_sClientFile ); |
|
if ( V_stristr( pClientPath, pMatchPattern ) != pClientPath ) |
|
continue; |
|
|
|
char pLocalPath[MAX_PATH]; |
|
V_ComposeFileName( s_p4.GetLocalRoot(), pClientPath + nLen, pLocalPath, sizeof(pLocalPath) ); |
|
V_FixSlashes( pLocalPath ); |
|
fileList[i].m_sLocalFile = pLocalPath; |
|
} |
|
} |
|
|
|
void ComputeLocalDirectoryNames( CUtlVector<P4File_t> &fileList ) |
|
{ |
|
// Need to construct valid local paths since opened doesn't do it for us |
|
int nCount = fileList.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( !fileList[i].m_bDir ) |
|
continue; |
|
|
|
char pLocalPath[MAX_PATH]; |
|
const char *pDepotPath = s_p4.String( fileList[i].m_sDepotFile ); |
|
if ( !s_p4.DepotFileToLocalFile( pDepotPath, pLocalPath, sizeof(pLocalPath) ) ) |
|
continue; |
|
|
|
fileList[i].m_sLocalFile = pLocalPath; |
|
} |
|
} |
|
|
|
void OutputRecordInternal(P4File_t &file, const char *szCmd, const char *szInfo) |
|
{ |
|
if (!V_strcmp(szCmd, "headRev")) |
|
{ |
|
file.m_iHeadRevision = atoi(szInfo); |
|
} |
|
else if (!V_strcmp(szCmd, "haveRev")) |
|
{ |
|
file.m_iHaveRevision = atoi(szInfo); |
|
} |
|
else if (!V_strcmp(szCmd, "headAction") && !V_strcmp(szInfo, "delete")) |
|
{ |
|
file.m_bDeleted = true; |
|
} |
|
else if (!V_strcmp(szCmd, "action")) |
|
{ |
|
if (!V_strcmp(szInfo, "edit")) |
|
{ |
|
file.m_eOpenState = P4FILE_OPENED_FOR_EDIT; |
|
} |
|
else if (!V_strcmp(szInfo, "delete")) |
|
{ |
|
file.m_eOpenState = P4FILE_OPENED_FOR_DELETE; |
|
} |
|
else if (!V_strcmp(szInfo, "add")) |
|
{ |
|
file.m_eOpenState = P4FILE_OPENED_FOR_ADD; |
|
} |
|
else if (!V_strcmp(szInfo, "integrate")) |
|
{ |
|
file.m_eOpenState = P4FILE_OPENED_FOR_INTEGRATE; |
|
} |
|
} |
|
else if (!V_strcmp(szCmd, "change")) |
|
{ |
|
file.m_iChangelist = atoi(szInfo); |
|
} |
|
else if ( !V_strcmp( szCmd, "otherOpen" ) ) |
|
{ |
|
file.m_bOpenedByOther = atoi( szInfo ) != 0; |
|
} |
|
else if ( m_bChangesRecord && (!V_strcmp(szCmd, "user") || !V_strcmp(szCmd,"client") || !V_strcmp(szCmd, "time") || |
|
!V_strcmp(szCmd, "desc") || !V_strcmp(szCmd, "status") || !V_strcmp(szCmd, "oldChange") || |
|
!V_strcmp(szCmd,"type") || !V_strcmp(szCmd,"rev")) ) |
|
{ |
|
// ignore these, processing changelist description |
|
} |
|
else |
|
{ |
|
// extract the path |
|
char pFilePath[_MAX_PATH]; |
|
V_strncpy( pFilePath, szInfo, sizeof( pFilePath ) ); |
|
char *pFileName = V_strrchr(pFilePath, '/'); |
|
if (pFileName) |
|
{ |
|
++pFileName; |
|
} |
|
|
|
if ( !V_stricmp( szCmd, "dir" ) ) |
|
{ |
|
// new directory, add to the list |
|
file.m_sPath = pFilePath; |
|
file.m_sDepotFile = pFilePath; |
|
file.m_sName = pFileName; |
|
file.m_bDir = true; |
|
} |
|
else if ( !V_strcmp( szCmd, "depotFile" ) ) |
|
{ |
|
if ( m_bChangesRecord ) |
|
{ |
|
char pLocalPath[MAX_PATH]; |
|
if ( s_p4.DepotFileToLocalFile( szInfo, pLocalPath, sizeof(pLocalPath) ) ) |
|
{ |
|
file.m_sLocalFile = pLocalPath; |
|
} |
|
} |
|
char *pCruft = V_strstr( pFilePath, "//%%1" ); |
|
if (pCruft) |
|
*pCruft = 0; |
|
file.m_sDepotFile = pFilePath; |
|
// Now that we've stored off the depot file, split file into path + name |
|
if (pFileName) |
|
{ |
|
*(pFileName - 1) = 0; |
|
} |
|
|
|
file.m_sPath = pFilePath; |
|
file.m_sName = pFileName; |
|
file.m_bDir = false; |
|
} |
|
else if (!V_stricmp(szCmd, "clientFile")) |
|
{ |
|
char *pCruft = V_strstr( pFilePath, "//%%1" ); |
|
if (pCruft) |
|
*pCruft = 0; |
|
file.m_sClientFile = pFilePath; |
|
} |
|
else if (!V_stricmp(szCmd, "path")) |
|
{ |
|
char *pCruft = V_strstr(pFilePath, "\\%%1"); |
|
if (pCruft) |
|
*pCruft = 0; |
|
file.m_sLocalFile = pFilePath; |
|
} |
|
} |
|
} |
|
virtual void OutputRecord(P4File_t &file, const char *szCmd, const char *szInfo) |
|
{ |
|
P4File_t *pFile = &file; |
|
if ( m_bChangesRecord ) |
|
{ |
|
char tmpCmd[1024]; |
|
int end = V_strlen(szCmd)-1; |
|
if ( end < ARRAYSIZE(tmpCmd) ) |
|
{ |
|
const char *pChar = szCmd + end; |
|
while ( isdigit(*pChar) && pChar > szCmd ) |
|
{ |
|
pChar--; |
|
} |
|
int pos = pChar - szCmd; |
|
if ( pos > 0 && pos < end ) |
|
{ |
|
int newChangeIndex = atoi(pChar+1); |
|
V_strncpy( tmpCmd, szCmd, sizeof(tmpCmd) ); |
|
tmpCmd[pos+1] = 0; |
|
szCmd = tmpCmd; |
|
if ( newChangeIndex != m_nChangesIndex ) |
|
{ |
|
pFile = ForceNextRecord(); |
|
m_nChangesIndex = newChangeIndex; |
|
} |
|
} |
|
} |
|
} |
|
OutputRecordInternal( *pFile, szCmd, szInfo ); |
|
} |
|
|
|
int m_nChangesIndex; |
|
bool m_bChangesRecord; |
|
}; |
|
CFileUser g_FileUser; |
|
CFileUser g_WhereUser; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retrieves a history list |
|
//----------------------------------------------------------------------------- |
|
class CRevisionHistoryUser : public CDataRetrievalUser<P4Revision_t> |
|
{ |
|
public: |
|
void RetrieveHistory(const char *path, bool bDir) |
|
{ |
|
InitRetrievingData(); |
|
|
|
// search for the paths - this string gets all revisions of a file, |
|
// from the version they have locally to the current version |
|
// this is so we can see if the local files are out of date |
|
char szSearch[MAX_PATH]; |
|
if (bDir) |
|
{ |
|
V_snprintf(szSearch, sizeof(szSearch), "%s/...", path); |
|
} |
|
else |
|
{ |
|
V_snprintf(szSearch, sizeof(szSearch), "%s", path); |
|
} |
|
|
|
// set to view long output, to show the time, and to have a maximum number of results |
|
char *argv[] = { "-l", "-t", "-m", "50", (char *)szSearch, NULL }; |
|
s_p4.GetClientApi().SetArgv( 5, argv ); |
|
s_p4.GetClientApi().Run("changes", this); |
|
|
|
} |
|
|
|
private: |
|
virtual void OutputRecord(P4Revision_t &revision, const char *szCmd, const char *szInfo) |
|
{ |
|
if (!V_strcmp(szCmd, "change")) |
|
{ |
|
revision.m_iChange = atoi(szInfo); |
|
} |
|
else if (!V_strcmp(szCmd, "user")) |
|
{ |
|
revision.m_sUser = szInfo; |
|
} |
|
else if (!V_strcmp(szCmd, "client")) |
|
{ |
|
revision.m_sClient = szInfo; |
|
} |
|
else if (!V_strcmp(szCmd, "status")) |
|
{ |
|
} |
|
else if (!V_strcmp(szCmd, "desc")) |
|
{ |
|
revision.m_Description = szInfo; |
|
} |
|
else if (!V_strcmp(szCmd, "time")) |
|
{ |
|
int64 iTime = atoi(szInfo); |
|
|
|
struct tm *gmt = gmtime(reinterpret_cast<time_t*>(&iTime)); |
|
|
|
revision.m_nYear = gmt->tm_year + 1900; |
|
revision.m_nMonth = gmt->tm_mon + 1; |
|
revision.m_nDay = gmt->tm_mday; |
|
revision.m_nHour = gmt->tm_hour; |
|
revision.m_nMinute = gmt->tm_min; |
|
revision.m_nSecond = gmt->tm_sec; |
|
} |
|
} |
|
}; |
|
CRevisionHistoryUser g_RevisionHistoryUser; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retreives list of clients |
|
//----------------------------------------------------------------------------- |
|
class CClientSpecUser : public CDataRetrievalUser<P4Client_t> |
|
{ |
|
public: |
|
void RetrieveClients() |
|
{ |
|
InitRetrievingData(); |
|
|
|
// we just get the entire list |
|
s_p4.GetClientApi().Run("clients", this); |
|
} |
|
|
|
private: |
|
virtual void OutputRecord(P4Client_t &client, const char *szCmd, const char *szInfo) |
|
{ |
|
if (!V_strcmp(szCmd, "client")) |
|
{ |
|
client.m_sName = szInfo; |
|
} |
|
else if (!V_strcmp(szCmd, "Owner")) |
|
{ |
|
client.m_sUser = szInfo; |
|
} |
|
else if (!V_strcmp(szCmd, "Root")) |
|
{ |
|
client.m_sLocalRoot = szInfo; |
|
} |
|
else if (!V_strcmp(szCmd, "Host")) |
|
{ |
|
client.m_sHost = szInfo; |
|
} |
|
else |
|
{ |
|
Msg("Unknown field %s = %s\n", szCmd, szInfo); |
|
} |
|
} |
|
}; |
|
CClientSpecUser g_ClientspecUser; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: holder and modifier of clientspec file mapings |
|
//----------------------------------------------------------------------------- |
|
class CClientspecMap |
|
{ |
|
public: |
|
char m_szFullClientspec[CLIENTSPEC_BUFFER_SIZE]; |
|
int m_iFullClientspecWritePosition; |
|
CUtlVector<CClientPathRecord> m_PathMap; |
|
char m_szLocalPath[_MAX_PATH]; |
|
|
|
void ResetFullClientSpec( void ) |
|
{ |
|
m_szFullClientspec[0] = '\0'; |
|
m_iFullClientspecWritePosition = 0; |
|
m_PathMap.RemoveAll(); |
|
m_szLocalPath[0] = '\0'; |
|
} |
|
|
|
void AddVarToFullClientSpec( const char *variable, const char *data ) |
|
{ |
|
V_snprintf( &m_szFullClientspec[m_iFullClientspecWritePosition], CLIENTSPEC_BUFFER_SIZE - m_iFullClientspecWritePosition, "%s: %s\n\n", variable, data ); |
|
m_iFullClientspecWritePosition += strlen( &m_szFullClientspec[m_iFullClientspecWritePosition] ); |
|
} |
|
|
|
void AddStringToFullClientSpec( const char *szString ) |
|
{ |
|
V_strncpy( &m_szFullClientspec[m_iFullClientspecWritePosition], szString, CLIENTSPEC_BUFFER_SIZE - m_iFullClientspecWritePosition ); |
|
m_iFullClientspecWritePosition += strlen( &m_szFullClientspec[m_iFullClientspecWritePosition] ); |
|
} |
|
|
|
void ReadRoot( const char *clientRoot ) |
|
{ |
|
V_strncpy( m_szLocalPath, clientRoot, _MAX_PATH ); |
|
} |
|
|
|
void ReadViewLine( const char *viewLine ) |
|
{ |
|
CUtlBuffer parse(viewLine, V_strlen(viewLine), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); |
|
|
|
CClientPathRecord record; |
|
record.m_bNegative = false; |
|
|
|
// get the depot path |
|
parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath)); |
|
if (V_strlen(record.m_szDepotPath) < 1) |
|
return; |
|
|
|
if (!V_stricmp(record.m_szDepotPath, "-")) |
|
{ |
|
// it's the negation sign, get the next line |
|
parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath)); |
|
record.m_bNegative = true; |
|
} |
|
if (record.m_szDepotPath[0] == '-') |
|
{ |
|
// negation sign is stuck on the end, remove |
|
record.m_bNegative = true; |
|
memmove(record.m_szDepotPath, record.m_szDepotPath + 1, sizeof(record.m_szDepotPath) - 1); |
|
} |
|
|
|
// get the next sign |
|
parse.GetString(record.m_szClientPath, sizeof(record.m_szClientPath)); |
|
|
|
// append to list |
|
m_PathMap.AddToTail(record); |
|
} |
|
|
|
void ReadClientspec(const char *clientspec) |
|
{ |
|
// Get the local path |
|
char *pszSearch = "\n\nRoot:"; |
|
char *pStr = V_strstr( clientspec, pszSearch ); |
|
if (pStr) |
|
{ |
|
pStr += V_strlen( pszSearch ); |
|
|
|
// parse out next satring |
|
CUtlBuffer parse(pStr, V_strlen(pStr), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); |
|
|
|
// get the local path |
|
parse.GetString( m_szLocalPath, sizeof(m_szLocalPath) ); |
|
} |
|
|
|
pszSearch = "\n\nView:\n"; |
|
pStr = V_strstr(clientspec, pszSearch); |
|
if (pStr) |
|
{ |
|
pStr += V_strlen(pszSearch); |
|
|
|
// take a full copy of the string |
|
V_strncpy(m_szFullClientspec, clientspec, sizeof(m_szFullClientspec)); |
|
|
|
// parse out strings |
|
CUtlBuffer parse(pStr, V_strlen(pStr), CUtlBuffer::TEXT_BUFFER|CUtlBuffer::READ_ONLY); |
|
|
|
while (parse.IsValid()) |
|
{ |
|
CClientPathRecord record; |
|
record.m_bNegative = false; |
|
|
|
// get the depot path |
|
parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath)); |
|
if (V_strlen(record.m_szDepotPath) < 1) |
|
break; |
|
|
|
if (!V_stricmp(record.m_szDepotPath, "-")) |
|
{ |
|
// it's the negation sign, get the next line |
|
parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath)); |
|
record.m_bNegative = true; |
|
} |
|
if (record.m_szDepotPath[0] == '-') |
|
{ |
|
// negation sign is stuck on the end, remove |
|
record.m_bNegative = true; |
|
memmove(record.m_szDepotPath, record.m_szDepotPath + 1, sizeof(record.m_szDepotPath) - 1); |
|
} |
|
|
|
// get the next sign |
|
parse.GetString(record.m_szClientPath, sizeof(record.m_szClientPath)); |
|
|
|
// append to list |
|
m_PathMap.AddToTail(record); |
|
} |
|
} |
|
} |
|
|
|
void WriteClientspec(char *pszDest, int destSize) |
|
{ |
|
// assemble the new view |
|
char szView[CLIENTSPEC_BUFFER_SIZE] = { 0 }; |
|
for (int i = 0; i < m_PathMap.Count(); i++) |
|
{ |
|
CClientPathRecord &record = m_PathMap[i]; |
|
|
|
V_strncat(szView, " ", sizeof(szView), COPY_ALL_CHARACTERS); |
|
if (record.m_bNegative) |
|
{ |
|
V_strncat(szView, "-", sizeof(szView), COPY_ALL_CHARACTERS); |
|
} |
|
V_strncat(szView, record.m_szDepotPath, sizeof(szView), COPY_ALL_CHARACTERS); |
|
V_strncat(szView, " ", sizeof(szView), COPY_ALL_CHARACTERS); |
|
V_strncat(szView, record.m_szClientPath, sizeof(szView), COPY_ALL_CHARACTERS); |
|
V_strncat(szView, " \n", sizeof(szView), COPY_ALL_CHARACTERS); |
|
} |
|
|
|
// find the place the view is set |
|
char *pszSearch = "\n\nView:\n"; |
|
char *pStr = V_strstr(m_szFullClientspec, pszSearch); |
|
if (pStr) |
|
{ |
|
char *pDest = (pStr + strlen(pszSearch)); |
|
|
|
// replace the view with the new view |
|
V_strncpy(pDest, szView, sizeof(m_szFullClientspec) - (pDest - m_szFullClientspec)); |
|
} |
|
|
|
// emit |
|
V_strncpy(pszDest, m_szFullClientspec, destSize); |
|
} |
|
|
|
void RemovePathFromClient(const char *path) |
|
{ |
|
// work out how the path record contains the specified path |
|
for (int i = 0; i < m_PathMap.Count(); i++) |
|
{ |
|
// look for the first path that matches, comparing the two strings up to before the "/..." in szDepotPath |
|
if (!V_strnicmp(m_PathMap[i].m_szDepotPath, path, V_strlen(m_PathMap[i].m_szDepotPath) - 4)) |
|
{ |
|
CClientPathRecord &baseRecord = m_PathMap[i]; |
|
|
|
// we have a match, build the negative case |
|
CClientPathRecord negativeRecord; |
|
negativeRecord.m_bNegative = true; |
|
V_snprintf(negativeRecord.m_szDepotPath, sizeof(negativeRecord.m_szDepotPath), "%s/...", path); |
|
|
|
// build the clientspec side of the mapping |
|
|
|
// aa/b/... //client/BAH/... |
|
//- aa/b/d/... //client/BAH/d/ |
|
|
|
// find the common strings in both |
|
int iPos = 0; |
|
while (baseRecord.m_szDepotPath[iPos] == negativeRecord.m_szDepotPath[iPos]) |
|
++iPos; |
|
|
|
char *pszNegPaths = negativeRecord.m_szDepotPath + iPos; |
|
|
|
// append the neg paths to the existing baseRecord.szClientPath |
|
V_strncpy(negativeRecord.m_szClientPath, baseRecord.m_szClientPath, sizeof(negativeRecord.m_szClientPath)); |
|
// strip off the last slash |
|
char *pszDir = V_strstr(negativeRecord.m_szClientPath, "/..."); |
|
if (pszDir) |
|
{ |
|
*pszDir = 0; |
|
} |
|
|
|
// append the new client paths to negate |
|
V_strncat(negativeRecord.m_szClientPath, "/", sizeof(negativeRecord.m_szClientPath), COPY_ALL_CHARACTERS); |
|
V_strncat(negativeRecord.m_szClientPath, pszNegPaths, sizeof(negativeRecord.m_szClientPath), COPY_ALL_CHARACTERS); |
|
|
|
m_PathMap.InsertAfter(i, negativeRecord); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// calculates the depot root |
|
void GetCommonDepotRoot(char *pszDest, int destSize) |
|
{ |
|
int nCount = m_PathMap.Count(); |
|
if ( nCount == 0 ) |
|
{ |
|
pszDest[0] = 0; |
|
return; |
|
} |
|
|
|
// walk each mapping, and find the longest common starting substring |
|
V_strncpy(pszDest, m_PathMap[0].m_szDepotPath, destSize); |
|
for (int i = 1; i < nCount; i++) |
|
{ |
|
if ( m_PathMap[i].m_bNegative ) |
|
continue; |
|
|
|
// see how much we compare |
|
for (char *pszD = pszDest, *pszS = m_PathMap[i].m_szDepotPath; *pszD && *pszS; ++pszD, ++pszS) |
|
{ |
|
if (*pszD != *pszS) |
|
{ |
|
*pszD = 0; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// remove any "..." at the end |
|
char *pszD = V_strstr(pszDest, "..."); |
|
if (pszD) |
|
{ |
|
*pszD = 0; |
|
} |
|
} |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: tool for editing clientspecs |
|
//----------------------------------------------------------------------------- |
|
class CClientspecEditUser : public ClientUser |
|
{ |
|
public: |
|
CClientspecEditUser( void ) : m_bReadDataVar( false ) {}; |
|
void RetrieveClient(const char *pszClient) |
|
{ |
|
m_Map.ResetFullClientSpec(); |
|
|
|
// we just get the entire list |
|
char *argv[] = { "-o", (char *)pszClient, NULL }; |
|
s_p4.GetClientApi().SetArgv( 2, argv ); |
|
s_p4.GetClientApi().Run("client", this); |
|
} |
|
|
|
// saves out the new path spec to the |
|
void WriteClientspec() |
|
{ |
|
// write out the new clientspec |
|
char *argv[] = { "-i", NULL, NULL }; |
|
s_p4.GetClientApi().SetArgv( 1, argv ); |
|
s_p4.GetClientApi().Run("client", this); |
|
} |
|
|
|
CClientspecMap m_Map; |
|
bool m_bReadDataVar; //if we encountered a "data" variable, turn off the other parsing |
|
|
|
private: |
|
virtual void OutputInfo( char level, const char *data ) |
|
{ |
|
char szVar[CLIENTSPEC_BUFFER_SIZE], szInfo[CLIENTSPEC_BUFFER_SIZE]; |
|
SplitP4Output(data, szVar, szInfo, sizeof(szInfo)); |
|
|
|
if( szVar[0] != '\0' ) |
|
{ |
|
if (!V_stricmp(szVar, "data")) |
|
{ |
|
m_bReadDataVar = true; |
|
m_Map.ReadClientspec(szInfo); |
|
} |
|
else if( !m_bReadDataVar ) |
|
{ |
|
if( !V_stricmp( szVar, "root" ) ) |
|
{ |
|
m_Map.ReadRoot( szInfo ); |
|
m_Map.AddVarToFullClientSpec( szVar, szInfo ); |
|
} |
|
else if( !V_strnicmp( szVar, "view", 4 ) ) |
|
{ |
|
if( !V_stricmp( szVar, "view" ) || !V_stricmp( szVar, "view0" ) ) |
|
{ |
|
//cheat a bit |
|
m_Map.AddStringToFullClientSpec( "View:\n" ); |
|
} |
|
m_Map.ReadViewLine( szInfo ); |
|
m_Map.AddStringToFullClientSpec( "\t" ); |
|
m_Map.AddStringToFullClientSpec( szInfo ); |
|
m_Map.AddStringToFullClientSpec( "\n" ); |
|
} |
|
else |
|
{ |
|
m_Map.AddVarToFullClientSpec( szVar, szInfo ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// called to read data |
|
virtual void InputData( StrBuf *strbuf, Error *e ) |
|
{ |
|
char szView[CLIENTSPEC_BUFFER_SIZE]; |
|
m_Map.WriteClientspec(szView, sizeof(szView)); |
|
strbuf->Set(szView); |
|
e->Clear(); |
|
} |
|
|
|
virtual void OutputError( const char *errBuf ) |
|
{ |
|
Msg("s_p4 error: %s", errBuf); |
|
} |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retreives list of clients |
|
//----------------------------------------------------------------------------- |
|
class CInfoUser : public ClientUser |
|
{ |
|
public: |
|
void RetrieveInfo() |
|
{ |
|
memset(&m_Client, 0, sizeof(m_Client)); |
|
|
|
// we just get the entire list |
|
s_p4.GetClientApi().Run("info", this); |
|
} |
|
|
|
P4Client_t m_Client; |
|
|
|
private: |
|
virtual void OutputInfo(char level, const char *data) |
|
{ |
|
char szCmd[_MAX_PATH]; |
|
char szInfo[_MAX_PATH]; |
|
|
|
SplitP4Output(data, szCmd, szInfo, sizeof(szCmd)); |
|
|
|
if (!V_stricmp(szCmd, "userName")) |
|
{ |
|
m_Client.m_sUser = szInfo; |
|
} |
|
else if (!V_stricmp(szCmd, "clientName")) |
|
{ |
|
m_Client.m_sName = szInfo; |
|
} |
|
else if (!V_stricmp(szCmd, "clientHost")) |
|
{ |
|
m_Client.m_sHost = szInfo; |
|
} |
|
else if (!V_stricmp(szCmd, "clientRoot")) |
|
{ |
|
m_Client.m_sLocalRoot = szInfo; |
|
} |
|
} |
|
}; |
|
CInfoUser g_InfoUser; |
|
|
|
|
|
// Changelist description structure |
|
struct ChangelistDesc_t |
|
{ |
|
int id; |
|
time_t m_tTimeStamp; |
|
CUtlSymbol m_sUser; |
|
CUtlSymbol m_sClient; |
|
CUtlSymbol m_sStatus; |
|
CUtlSymbol m_sDescription; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: tool for creating new changelists |
|
//----------------------------------------------------------------------------- |
|
class CChangelistCreateUser : public CDataRetrievalUser<int> |
|
{ |
|
public: |
|
void CreateChangelist( const char *pDescription ) |
|
{ |
|
InitRetrievingData(); |
|
|
|
// Will force InputData to be called |
|
m_pDescription = ( pDescription && pDescription[0] ) ? pDescription : "I'm a loser who didn't type a description. Mock me at your earliest convenience."; |
|
char *argv[] = { "-i", NULL }; |
|
s_p4.GetClientApi().SetArgv( 1, argv ); |
|
s_p4.GetClientApi().Run("change", this); |
|
} |
|
|
|
private: |
|
// called to read data from stdin |
|
virtual void InputData( StrBuf *strbuf, Error *e ) |
|
{ |
|
char svChangelist[ P4_MAX_INPUT_BUFFER_SIZE + 2048 ]; |
|
P4Client_t &activeClient = s_p4.GetActiveClient(); |
|
|
|
V_snprintf( svChangelist, sizeof(svChangelist), |
|
"Change:\tnew\n\nClient:\t%s\n\nUser:\t%s\n\nStatus:\tnew\n\n" |
|
"Description:\n\t%s\n\nFiles:\n\n", |
|
activeClient.m_sName.String(), activeClient.m_sUser.String(), m_pDescription ); |
|
|
|
strbuf->Set(svChangelist); |
|
e->Clear(); |
|
} |
|
|
|
virtual void OutputRecord( int &nChangelist, const char *szCmd, const char *szInfo) |
|
{ |
|
if (!V_strcmp(szCmd, "Change")) |
|
{ |
|
nChangelist = atoi(szInfo); |
|
} |
|
} |
|
|
|
const char *m_pDescription; |
|
}; |
|
CChangelistCreateUser g_ChangelistCreateUser; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: tool for finding an existing changelist |
|
//----------------------------------------------------------------------------- |
|
class CChangelistFindUser : public CDataRetrievalUser<ChangelistDesc_t> |
|
{ |
|
public: |
|
void ListChangelists( void ) |
|
{ |
|
InitRetrievingData(); |
|
|
|
char *argv[] = { "-l", "-t", "-s", "pending", "-c", s_p4.GetClientApi().GetClient().Text(), NULL }; |
|
s_p4.GetClientApi().SetArgv( 6, argv ); |
|
s_p4.GetClientApi().Run("changes", this); |
|
} |
|
|
|
private: |
|
virtual void OutputRecord( ChangelistDesc_t &cl, const char *szCmd, const char *szInfo) |
|
{ |
|
if ( !V_strcmp( szCmd, "change" ) ) |
|
{ |
|
cl.id = atoi( szInfo ); |
|
} |
|
else if ( !V_strcmp( szCmd, "time" ) ) |
|
{ |
|
cl.m_tTimeStamp = atoi( szInfo ); |
|
} |
|
else if ( !V_strcmp( szCmd, "user" ) ) |
|
{ |
|
cl.m_sUser = szInfo; |
|
} |
|
else if ( !V_strcmp( szCmd, "client" ) ) |
|
{ |
|
cl.m_sClient = szInfo; |
|
} |
|
else if ( !V_strcmp( szCmd, "status" ) ) |
|
{ |
|
cl.m_sStatus = szInfo; |
|
} |
|
else if ( !V_strcmp( szCmd, "desc" ) ) |
|
{ |
|
CUtlVector<char> arrInfo; |
|
arrInfo.SetCount( 1 + strlen( szInfo ) ); |
|
memcpy( arrInfo.Base(), szInfo, arrInfo.Count() ); |
|
for ( int nCount = arrInfo.Count() - 1; nCount -- > 0; ) |
|
{ |
|
if ( isspace( arrInfo[ nCount ] ) ) |
|
arrInfo[ nCount ] = 0; |
|
else |
|
break; |
|
} |
|
|
|
cl.m_sDescription = arrInfo.Base(); |
|
} |
|
} |
|
}; |
|
CChangelistFindUser g_ChangelistFindUser; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: only used to handle the error callback |
|
//----------------------------------------------------------------------------- |
|
class CErrorHandlerUser : public ClientUser |
|
{ |
|
public: |
|
CErrorHandlerUser() : m_errorSeverity( E_EMPTY ), m_uiFlags( 0 ) {} |
|
|
|
virtual void OutputError( const char *errBuf ) |
|
{ |
|
m_errorSeverity = max( m_errorSeverity, E_WARN ); // this is a guess - it could have been E_FATAL or E_FAILED |
|
|
|
Msg("s_p4 error: %s", errBuf); |
|
|
|
m_errorBuf.Append( errBuf ); |
|
} |
|
virtual void HandleError( Error *err ) |
|
{ |
|
m_errorSeverity = max( m_errorSeverity, ( ErrorSeverity )err->GetSeverity() ); |
|
|
|
StrBuf buf; |
|
err->Fmt( buf, EF_NEWLINE ); |
|
|
|
if ( V_strstr( buf.Text(), "can't edit exclusive file already opened" ) ) |
|
{ |
|
m_errorSeverity = max( m_errorSeverity, E_WARN ); // s_p4 sends this is an info message, even though the file doesn't get edited! |
|
} |
|
|
|
if ( ( m_uiFlags & eDisableFiltering ) || ShallOutputErrorStringBuffer( buf ) ) |
|
Msg( "%s: %s", err->FmtSeverity(), buf.Text() ); |
|
|
|
m_errorBuf.Append( &buf ); |
|
} |
|
|
|
// Message is the only method that gets called on a s_p4 error |
|
// even though the s_p4 documentation claims that HandleError (and therefore OutputError) |
|
// should get called via the defalt Message implementation |
|
virtual void Message( Error *err ) |
|
{ |
|
HandleError( err ); |
|
} |
|
|
|
void ResetErrorState() |
|
{ |
|
m_errorSeverity = E_EMPTY; |
|
m_errorBuf.Clear(); |
|
} |
|
ErrorSeverity GetErrorState() |
|
{ |
|
return m_errorSeverity; |
|
} |
|
const char *GetErrorString() |
|
{ |
|
return m_errorBuf.Text(); |
|
} |
|
void SetErrorString( const char *errStr, ErrorSeverity severity ) |
|
{ |
|
m_errorBuf.Set( errStr ); |
|
m_errorSeverity = severity; |
|
} |
|
|
|
enum Flags { |
|
eDisableFiltering = 1 << 0, // Disable filtering output |
|
eFilterClUselessSpew = 1 << 1, // Filter "already opened", "add of exisiting", "can't change" msgs |
|
}; |
|
|
|
// Sets the new flag mask, add prevails over remove if the flag specified in both. |
|
// SetFlags( x, 0 ) - enables flag x |
|
// SetFlags( 0, x ) - removes flag x |
|
// SetFlags( mask, ~0 ) - completely sets the flag mask |
|
uint32 SetFlags( uint32 uiAdd, uint32 uiRemove ) { uint32 uiOld = m_uiFlags; m_uiFlags = ( ( m_uiFlags & ~uiRemove ) | uiAdd ); return uiOld; } |
|
|
|
protected: |
|
// Performs filtering before the message is output to screen, |
|
// if "ShallOutputErrorStringBuffer" returns false, then the message |
|
// is not printed on screen, otherwise the modified strbug is printed. |
|
virtual bool ShallOutputErrorStringBuffer( StrBuf &buf ); |
|
|
|
private: |
|
ErrorSeverity m_errorSeverity; |
|
StrBuf m_errorBuf; |
|
uint32 m_uiFlags; |
|
}; |
|
CErrorHandlerUser g_ErrorHandlerUser; |
|
|
|
bool CErrorHandlerUser::ShallOutputErrorStringBuffer( StrBuf &buf ) |
|
{ |
|
char const *pText = buf.Text(); |
|
|
|
if ( m_uiFlags & eFilterClUselessSpew ) |
|
{ |
|
if ( V_strstr( pText, "already opened for edit" ) ) |
|
return false; |
|
if ( V_strstr( pText, "already opened for add" ) ) |
|
return false; |
|
if ( V_strstr( pText, "currently opened for edit" ) ) |
|
return false; |
|
if ( V_strstr( pText, "currently opened for add" ) ) |
|
return false; |
|
if ( V_strstr( pText, "add of existing file" ) ) |
|
return false; |
|
if ( V_strstr( pText, "add existing file" ) ) |
|
return false; |
|
if ( V_strstr( pText, "can't change from" ) ) |
|
return false; |
|
if ( V_strstr( pText, "no such file" ) ) |
|
return false; |
|
if ( V_strstr( pText, "not on client" ) ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: tool for creating new changelists |
|
//----------------------------------------------------------------------------- |
|
class CSubmitUser : public ClientUser |
|
{ |
|
public: |
|
void Submit( int nChangeList ) |
|
{ |
|
// NOTE: -i doesn't appear to work with -c |
|
// We have to set up the description when creating the changelist in the first place |
|
m_pDescription = NULL; |
|
char pBuf[128]; |
|
V_snprintf( pBuf, sizeof(pBuf), "%d", nChangeList ); |
|
char *argv[] = { "-c", pBuf, NULL }; |
|
s_p4.GetClientApi().SetArgv( 2, argv ); |
|
s_p4.GetClientApi().Run( "submit", &g_ErrorHandlerUser ); |
|
} |
|
|
|
private: |
|
// called to read data from stdin |
|
virtual void InputData( StrBuf *strbuf, Error *e ) |
|
{ |
|
Assert( m_pDescription ); |
|
|
|
char svChangelist[1024]; |
|
P4Client_t &activeClient = s_p4.GetActiveClient(); |
|
|
|
V_snprintf( svChangelist, sizeof(svChangelist), |
|
"Change:\tnew\n\nClient:\t%s\n\nUser:\t%s\n\nStatus:\tnew\n\n" |
|
"Description:\n\t%s\n\nFiles:\n\n", |
|
activeClient.m_sName.String(), activeClient.m_sUser.String(), m_pDescription ); |
|
|
|
strbuf->Set(svChangelist); |
|
e->Clear(); |
|
} |
|
|
|
const char *m_pDescription; |
|
}; |
|
CSubmitUser g_SubmitUser; |
|
|
|
|
|
int FindOrCreateChangelist( const char *pDescription ) |
|
{ |
|
int iCreatedEmptyCl = 0, iFoundChangelist = 0; |
|
CUtlMap<int, ChangelistDesc_t const *> mapFoundChangelists( DefLessFunc( int ) ); |
|
|
|
CUtlSymbol symDesc = pDescription; |
|
|
|
find_changelists: |
|
mapFoundChangelists.RemoveAll(); |
|
g_ChangelistFindUser.ListChangelists(); |
|
for ( int k = 0; k < g_ChangelistFindUser.GetData().Count(); ++ k ) |
|
{ |
|
ChangelistDesc_t const &cl = g_ChangelistFindUser.GetData()[ k ]; |
|
|
|
if ( symDesc == cl.m_sDescription ) |
|
mapFoundChangelists.Insert( cl.id, &cl ); |
|
} |
|
|
|
if ( mapFoundChangelists.Count() ) |
|
iFoundChangelist = mapFoundChangelists.Key( mapFoundChangelists.FirstInorder() ); |
|
else if ( iCreatedEmptyCl > 0 ) |
|
return iCreatedEmptyCl; |
|
|
|
if ( iFoundChangelist > 0 ) |
|
{ |
|
// Check if we created a changelist that has to be deleted |
|
if ( iCreatedEmptyCl > 0 && iCreatedEmptyCl != iFoundChangelist ) |
|
{ |
|
// Delete changelist |
|
char chClNumber[50]; |
|
sprintf( chClNumber, "%d", iCreatedEmptyCl ); |
|
char *argv[] = { "-d", chClNumber, NULL }; |
|
s_p4.GetClientApi().SetArgv( 2, argv ); |
|
s_p4.GetClientApi().Run( "change", &g_ErrorHandlerUser ); |
|
} |
|
|
|
return iFoundChangelist; |
|
} |
|
else |
|
{ |
|
// We did not find a changelist, create a new one |
|
g_ChangelistCreateUser.CreateChangelist( pDescription ); |
|
|
|
iCreatedEmptyCl = g_ChangelistCreateUser.GetData().Count() ? g_ChangelistCreateUser.GetData()[0] : 0; |
|
if ( !iCreatedEmptyCl ) |
|
return 0; |
|
|
|
// Now we have a changelist created, check again to avoid multithreading bugs |
|
goto find_changelists; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Destructor |
|
//----------------------------------------------------------------------------- |
|
CP4::~CP4() |
|
{ |
|
// Prevents a hang if Shutdown isn't called before the process exits |
|
if ( m_bConnectedToServer ) |
|
{ |
|
Shutdown(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Connect, disconnect |
|
//----------------------------------------------------------------------------- |
|
bool CP4::Connect( CreateInterfaceFn factory ) |
|
{ |
|
#if !defined(STANDALONE_VPC) |
|
g_pFileSystem = (IFileSystem*)factory( FILESYSTEM_INTERFACE_VERSION, NULL ); |
|
return ( g_pFileSystem != NULL ); |
|
#else |
|
return true; |
|
#endif |
|
} |
|
|
|
void CP4::Disconnect() |
|
{ |
|
#if !defined(STANDALONE_VPC) |
|
g_pFileSystem = NULL; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Startup |
|
//----------------------------------------------------------------------------- |
|
InitReturnVal_t CP4::Init() |
|
{ |
|
// set the protocol return all data as key/value pairs |
|
m_Client.SetProtocol( "tag", "" ); |
|
|
|
// connect to the s_p4 server |
|
Error e; |
|
m_Client.Init( &e ); |
|
m_bConnectedToServer = e.Test() == 0 && m_Client.Dropped() == 0; |
|
|
|
RefreshClientData(); |
|
|
|
return INIT_OK; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Cleanup |
|
//----------------------------------------------------------------------------- |
|
void CP4::Shutdown() |
|
{ |
|
Error e; |
|
m_Client.Final(&e); |
|
m_bConnectedToServer = false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: refreshes client data |
|
//----------------------------------------------------------------------------- |
|
void CP4::RefreshClientData() |
|
{ |
|
if ( !m_bConnectedToServer ) |
|
return; |
|
|
|
// retrieve our login info |
|
g_InfoUser.RetrieveInfo(); |
|
m_ActiveClient = g_InfoUser.m_Client; |
|
|
|
// calculate our common depot root |
|
CClientspecEditUser user; |
|
user.RetrieveClient( GetActiveClient().m_sName.String() ); |
|
|
|
user.m_Map.GetCommonDepotRoot(m_szDepotRoot, sizeof(m_szDepotRoot)); |
|
m_iDepotRootLength = V_strlen(m_szDepotRoot); |
|
m_ClientMapping = user.m_Map.m_PathMap; |
|
V_strncpy( m_szLocalRoot, user.m_Map.m_szLocalPath, sizeof(m_szLocalRoot) ); |
|
m_iLocalRootLength = V_strlen(m_szLocalRoot); |
|
|
|
SetOpenFileChangeList( m_sChangeListName.String() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Convert a depot file to a local file user the current client mapping |
|
//----------------------------------------------------------------------------- |
|
bool CP4::DepotFileToLocalFile( const char *pDepotFile, char *pLocalPath, int nBufLen ) |
|
{ |
|
// Need to construct valid local paths since opened doesn't do it for us |
|
char pClientPattern[MAX_PATH]; |
|
V_snprintf( pClientPattern, sizeof(pClientPattern), "//%s/", m_Client.GetClient().Text() ); |
|
int nClientLen = V_strlen( pClientPattern ); |
|
|
|
int nMatchCount = 0; |
|
int nCount = m_ClientMapping.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
const char *pDepotPath = m_ClientMapping[i].m_szDepotPath; |
|
int nLen = V_strlen( pDepotPath ); |
|
if ( nMatchCount > nLen ) |
|
continue; |
|
|
|
bool bRecursive = !V_stricmp( &pDepotPath[nLen-4], "/..." ); |
|
bool bInDirectory = !V_stricmp( &pDepotPath[nLen-2], "/*" ); |
|
if ( !bRecursive && !bInDirectory ) |
|
continue; |
|
|
|
// FIXME: Do this fixup when we create m_ClientMapping? |
|
char pMatchingString[MAX_PATH]; |
|
V_strncpy( pMatchingString, pDepotPath, bRecursive ? nLen-2 : nLen ); |
|
if ( V_stristr( pDepotFile, pMatchingString ) != pDepotFile ) |
|
continue; |
|
|
|
// Skip subdirectories if it's in the directory |
|
if ( bInDirectory ) |
|
{ |
|
const char *pRelativePath = pDepotFile + nLen - 1; |
|
if ( strchr( pRelativePath, '\\' ) || strchr( pRelativePath, '/' ) ) |
|
continue; |
|
} |
|
|
|
const char *pClientPath = s_p4.String( m_ClientMapping[i].m_szClientPath ); |
|
int nPathLen = V_strlen( pClientPath ); |
|
|
|
bool bClientRecursive = !V_stricmp( &pClientPath[nPathLen-4], "/..." ); |
|
bool bClientInDirectory = !V_stricmp( &pClientPath[nPathLen-2], "/*" ); |
|
if ( !bClientRecursive && !bClientInDirectory ) |
|
continue; |
|
|
|
char pTruncatedClientPath[MAX_PATH]; |
|
V_strncpy( pTruncatedClientPath, pClientPath, bClientRecursive ? nPathLen-2 : nPathLen ); |
|
if ( V_stristr( pTruncatedClientPath, pClientPattern ) != pTruncatedClientPath ) |
|
continue; |
|
|
|
// This is necessary if someone has a trailing slash on their root as in c:\valve\main\ |
|
// Otherwise it'll return something like c:\valve\main\\src\blah.cpp and confuse VPC. |
|
char szLocalRootWithoutSlashes[MAX_PATH]; |
|
V_strncpy( szLocalRootWithoutSlashes, s_p4.GetLocalRoot(), sizeof( szLocalRootWithoutSlashes ) ); |
|
V_StripTrailingSlash( szLocalRootWithoutSlashes ); |
|
|
|
if ( nClientLen < nPathLen - 3 ) |
|
{ |
|
V_snprintf( pLocalPath, nBufLen, "%s\\%s%s", szLocalRootWithoutSlashes, pTruncatedClientPath + nClientLen, bRecursive ? pDepotFile + nLen - 3 : pDepotFile + nLen - 1 ); |
|
} |
|
else |
|
{ |
|
V_snprintf( pLocalPath, nBufLen, "%s\\%s", szLocalRootWithoutSlashes, bRecursive ? pDepotFile + nLen - 3 : pDepotFile + nLen - 1 ); |
|
} |
|
V_FixSlashes( pLocalPath ); |
|
nMatchCount = nLen; |
|
} |
|
|
|
return ( nMatchCount > 0 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Refreshes the current client from s_p4 settings |
|
//----------------------------------------------------------------------------- |
|
void CP4::RefreshActiveClient() |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return; |
|
|
|
RefreshClientData(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Query interface |
|
//----------------------------------------------------------------------------- |
|
void *CP4::QueryInterface( const char *pInterfaceName ) |
|
{ |
|
return Sys_GetFactoryThis()( pInterfaceName, NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets a string for a symbol |
|
//----------------------------------------------------------------------------- |
|
const char *CP4::String( CUtlSymbol s ) const |
|
{ |
|
return s.String(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: data accessor |
|
//----------------------------------------------------------------------------- |
|
const char *CP4::GetDepotRoot() |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return NULL; |
|
|
|
return m_szDepotRoot; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: data accessor |
|
//----------------------------------------------------------------------------- |
|
int CP4::GetDepotRootLength() |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return -1; |
|
|
|
return m_iDepotRootLength; |
|
} |
|
|
|
const char *CP4::GetLocalRoot() |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return NULL; |
|
|
|
return m_szLocalRoot; |
|
} |
|
|
|
int CP4::GetLocalRootLength() |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return -1; |
|
|
|
return m_iLocalRootLength; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: translates filespecs to depotFile paths |
|
//----------------------------------------------------------------------------- |
|
void CP4::GetDepotFilePath(char *depotFilePath, const char *filespec, int size) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
if ( size > 0 ) |
|
{ |
|
depotFilePath[ 0 ] = '\0'; |
|
} |
|
return; |
|
} |
|
|
|
g_WhereUser.RetrieveWhereabouts(filespec); |
|
V_strncpy(depotFilePath, g_WhereUser.GetData()[0].m_sDepotFile.String(), size); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: translates filespecs to clientFile paths |
|
//----------------------------------------------------------------------------- |
|
void CP4::GetClientFilePath(char *clientFilePath, const char *filespec, int size) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
if ( size > 0 ) |
|
{ |
|
clientFilePath[ 0 ] = '\0'; |
|
} |
|
return; |
|
} |
|
|
|
g_WhereUser.RetrieveWhereabouts(filespec); |
|
V_strncpy(clientFilePath, g_WhereUser.GetData()[0].m_sClientFile.String(), size); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: translates filespecs to localFile paths |
|
//----------------------------------------------------------------------------- |
|
void CP4::GetLocalFilePath(char *localFilePath, const char *filespec, int size) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
if ( size > 0 ) |
|
{ |
|
localFilePath[ 0 ] = '\0'; |
|
} |
|
return; |
|
} |
|
|
|
g_WhereUser.RetrieveWhereabouts(filespec); |
|
V_strncpy(localFilePath, g_WhereUser.GetData()[0].m_sLocalFile.String(), size); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns a list of files |
|
//----------------------------------------------------------------------------- |
|
CUtlVector<P4File_t> &CP4::GetFileList( const char *pPath ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
static CUtlVector< P4File_t > dummy; |
|
return dummy; |
|
} |
|
|
|
CScopedDirClientSpec spec( pPath ); |
|
g_FileUser.RetrieveDir( pPath ); |
|
return g_FileUser.GetData(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// retreives the list of files in a path, using a known client spec |
|
//----------------------------------------------------------------------------- |
|
CUtlVector<P4File_t> &CP4::GetFileListUsingClientSpec( const char *pPath, const char *pClientSpec ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
static CUtlVector< P4File_t > dummy; |
|
return dummy; |
|
} |
|
|
|
CScopedClientSpec spec( pClientSpec ); |
|
g_FileUser.RetrieveDir( pPath ); |
|
return g_FileUser.GetData(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns the list of files opened for edit/integrate/delete |
|
//----------------------------------------------------------------------------- |
|
void CP4::GetOpenedFileList( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
fileList.RemoveAll(); |
|
return; |
|
} |
|
|
|
g_FileUser.RetrieveOpenedFiles( fileList, bDefaultChangeOnly ); |
|
} |
|
|
|
void CP4::GetFileListInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
fileList.RemoveAll(); |
|
return; |
|
} |
|
g_FileUser.RetrieveFilesInChangelist( changeListNumber, fileList ); |
|
} |
|
|
|
|
|
void CP4::GetOpenedFileList( const char *pRootDirectory, CUtlVector<P4File_t> &fileList ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
fileList.RemoveAll(); |
|
return; |
|
} |
|
|
|
CScopedDirClientSpec spec( pRootDirectory ); |
|
g_FileUser.RetrieveOpenedFiles( fileList, false ); |
|
} |
|
|
|
void CP4::GetOpenedFileListInPath( const char *pPathID, CUtlVector<P4File_t> &fileList ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
fileList.RemoveAll(); |
|
return; |
|
} |
|
|
|
CScopedPathClientSpec spec( pPathID ); |
|
g_FileUser.RetrieveOpenedFiles( fileList, false ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: file history |
|
//----------------------------------------------------------------------------- |
|
CUtlVector<P4Revision_t> &CP4::GetRevisionList(const char *path, bool bIsDir) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
static CUtlVector< P4Revision_t > dummy; |
|
return dummy; |
|
} |
|
|
|
g_RevisionHistoryUser.RetrieveHistory(path, bIsDir); |
|
return g_RevisionHistoryUser.GetData(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns a list of clients |
|
//----------------------------------------------------------------------------- |
|
CUtlVector<P4Client_t> &CP4::GetClientList() |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
static CUtlVector< P4Client_t > dummy; |
|
return dummy; |
|
} |
|
|
|
g_ClientspecUser.RetrieveClients(); |
|
return g_ClientspecUser.GetData(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: sets the active client |
|
//----------------------------------------------------------------------------- |
|
void CP4::SetActiveClient(const char *clientname) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return; |
|
|
|
m_Client.SetClient(clientname); |
|
RefreshClientData(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns the name of the currently opened clientspec |
|
//----------------------------------------------------------------------------- |
|
P4Client_t &CP4::GetActiveClient() |
|
{ |
|
return m_ActiveClient; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: cloaks a folder from the current view |
|
//----------------------------------------------------------------------------- |
|
void CP4::RemovePathFromActiveClientspec(const char *path) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return; |
|
|
|
// read in the clientspec |
|
CClientspecEditUser user; |
|
user.RetrieveClient(GetActiveClient().m_sName.String()); |
|
|
|
// get the extra info |
|
user.m_Map.RemovePathFromClient(path); |
|
|
|
// refresh the list |
|
user.WriteClientspec(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a client spec for a directory. Is destructive to the passed-in directory |
|
//----------------------------------------------------------------------------- |
|
bool CP4::GetClientSpecForDirectory( const char *pFullPathDir, char *pClientSpec, int nMaxLen ) |
|
{ |
|
if ( nMaxLen == 0 ) |
|
return false; |
|
|
|
pClientSpec[ 0 ] = '\0'; |
|
|
|
if ( !IsConnectedToServer() ) |
|
return false; |
|
|
|
char pCurPath[ MAX_PATH ]; |
|
V_strncpy( pCurPath, pFullPathDir, sizeof(pCurPath) ); |
|
V_StripTrailingSlash( pCurPath ); |
|
characterset_t breaks; |
|
CharacterSetBuild( &breaks, "=\n"); |
|
do |
|
{ |
|
char pP4ConfigPath[MAX_PATH]; |
|
V_strncpy( pP4ConfigPath, pCurPath, MAX_PATH ); |
|
V_strncat( pP4ConfigPath, "\\p4config", MAX_PATH, MAX_PATH ); |
|
if ( FileExists( pP4ConfigPath ) ) |
|
{ |
|
char temp[1024]; |
|
CUtlBuffer buf( temp, sizeof(temp), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE ); |
|
if ( ReadFile( pP4ConfigPath, NULL, buf ) ) |
|
{ |
|
while ( buf.IsValid() ) |
|
{ |
|
char token[256], value[256]; |
|
if ( !buf.ParseToken( &breaks, token, sizeof(token) ) ) |
|
break; |
|
if ( !buf.GetToken("=") ) |
|
break; |
|
if ( !buf.ParseToken( &breaks, value, sizeof(value) ) ) |
|
break; |
|
if ( !V_stricmp(token, "p4client") ) |
|
{ |
|
V_strncpy( pClientSpec, value, nMaxLen ); |
|
return true; |
|
} |
|
} |
|
|
|
Warning( "Unable to read file %s!\n", pP4ConfigPath ); |
|
} |
|
} |
|
|
|
V_StripLastDir( pCurPath, sizeof(pCurPath) ); |
|
V_StripTrailingSlash( pCurPath ); |
|
if ( V_strlen( pCurPath ) <= 2 ) |
|
break; |
|
|
|
} while (true); |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns which clientspec a file lies under |
|
//----------------------------------------------------------------------------- |
|
bool CP4::GetClientSpecForFile( const char *pFullPath, char *pClientSpec, int nMaxLen ) |
|
{ |
|
// Strip off the file name |
|
char pCurPath[MAX_PATH]; |
|
V_strncpy( pCurPath, pFullPath, sizeof(pCurPath) ); |
|
V_StripFilename( pCurPath ); |
|
|
|
// Recursively search subdirectories until we find a p4config file. |
|
return GetClientSpecForDirectory( pCurPath, pClientSpec, nMaxLen ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns which clientspec a filesystem path ID lies under |
|
//----------------------------------------------------------------------------- |
|
bool CP4::GetClientSpecForPath( const char *pPathId, char *pClientSpec, int nMaxLen ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
{ |
|
if ( nMaxLen > 0 ) |
|
{ |
|
pClientSpec[ 0 ] = '\0'; |
|
} |
|
return false; |
|
} |
|
|
|
char pPathBuf[2048]; |
|
#if defined( STANDALONE_VPC ) |
|
V_strncpy( pPathBuf, ".", V_ARRAYSIZE( pPathBuf ) ); |
|
#else |
|
if ( g_pFileSystem->GetSearchPath( pPathId, false, pPathBuf, sizeof(pPathBuf) ) == 0 ) |
|
return false; |
|
#endif |
|
// FIXME: Would be faster to not check for duplication and just |
|
// pick the first client spec it sees. Should I not test the additional paths? |
|
bool bFirstPath = true; |
|
bool bFoundClientSpec = false; |
|
char pTempClientSpec[MAX_PATH]; |
|
char *pPath = pPathBuf; |
|
while ( pPath ) |
|
{ |
|
// Find the next path |
|
char *pCurPath = pPath; |
|
char *pSemi = strchr( pPath, ';' ); |
|
if ( pSemi ) |
|
{ |
|
*pSemi = 0; |
|
pPath = pSemi+1; |
|
} |
|
else |
|
{ |
|
pPath = NULL; |
|
} |
|
|
|
// Recursively search subdirectories until we find a p4config file. |
|
if ( GetClientSpecForDirectory( pCurPath, pClientSpec, nMaxLen ) ) |
|
{ |
|
bFoundClientSpec = true; |
|
if ( bFirstPath ) |
|
{ |
|
V_strncpy( pTempClientSpec, pClientSpec, sizeof(pTempClientSpec) ); |
|
} |
|
else |
|
{ |
|
// Paths are on different client specs |
|
if ( V_stricmp( pClientSpec, pTempClientSpec ) ) |
|
{ |
|
pClientSpec[0] = 0; |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
return bFoundClientSpec; |
|
} |
|
|
|
void CP4::SetOpenFileChangeList( const char *pChangeListName ) |
|
{ |
|
if ( !m_sChangeListName.Length() || |
|
!pChangeListName || !*pChangeListName || |
|
strcmp( m_sChangeListName.String(), pChangeListName ) || |
|
pChangeListName == m_sChangeListName.String() ) |
|
{ |
|
m_sChangeListName.Set( pChangeListName ); |
|
m_sCachedChangeListNum.Set( "0" ); |
|
m_nCachedChangeListNumber = 0; |
|
|
|
// Valid changelist |
|
{ |
|
uint32 uiSpewFlag = CErrorHandlerUser::eFilterClUselessSpew; |
|
int bAdd = m_sChangeListName.Length(); |
|
g_ErrorHandlerUser.SetFlags( bAdd ? uiSpewFlag : 0, bAdd ? 0 : uiSpewFlag ); |
|
} |
|
} |
|
} |
|
|
|
char const * CP4::GetOpenFileChangeListNum() |
|
{ |
|
if ( m_sChangeListName.Length() ) |
|
{ |
|
if ( !m_nCachedChangeListNumber ) |
|
{ |
|
m_nCachedChangeListNumber = FindOrCreateChangelist( m_sChangeListName.String() ); |
|
if ( m_nCachedChangeListNumber ) |
|
m_sCachedChangeListNum.Format( "%d", m_nCachedChangeListNumber ); |
|
} |
|
|
|
if ( m_nCachedChangeListNumber ) |
|
return m_sCachedChangeListNum.String(); |
|
} |
|
return NULL; |
|
} |
|
|
|
static bool MakeFilesWritable( int nCount, const char **ppFullPathList ) |
|
{ |
|
bool bResult = true; |
|
|
|
// Make sure we can make as many files writable as possible |
|
for ( int k = 0; k < nCount; ++ k ) |
|
{ |
|
char const *szFile = ppFullPathList[ k ]; |
|
if ( !access( szFile, 02 ) ) |
|
continue; |
|
if ( !chmod( szFile, _S_IWRITE | _S_IREAD ) ) |
|
continue; |
|
bResult = false; |
|
} |
|
|
|
return bResult; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CP4::OpenFileForAdd( const char *fullpath ) |
|
{ |
|
return PerformPerforceOpCurChangeList( "add", fullpath ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CP4::OpenFileForEdit( const char *fullpath ) |
|
{ |
|
if ( !PerformPerforceOpCurChangeList( "edit", fullpath ) ) |
|
return false; |
|
|
|
return MakeFilesWritable( 1, &fullpath ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CP4::OpenFileForDelete( const char *pFullPath ) |
|
{ |
|
return PerformPerforceOpCurChangeList( "delete", pFullPath ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CP4::SyncFile( const char *pFullPath, int nRevision ) |
|
{ |
|
char szFileOptions[ MAX_PATH + 128 ]; |
|
|
|
if ( nRevision >= 0 ) |
|
{ // sync to a specific revision |
|
V_snprintf( szFileOptions, sizeof( szFileOptions ), "%s#%d", pFullPath, nRevision ); |
|
} |
|
else |
|
{ // sync to the head revision |
|
V_snprintf( szFileOptions, sizeof( szFileOptions ), "%s#head", pFullPath ); |
|
} |
|
|
|
return PerformPerforceOp( "sync", szFileOptions ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Submit file |
|
//----------------------------------------------------------------------------- |
|
bool CP4::SubmitFile( const char *pFullPath, const char *pDescription ) |
|
{ |
|
return SubmitFiles( 1, &pFullPath, pDescription ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Revert file |
|
//----------------------------------------------------------------------------- |
|
bool CP4::RevertFile( const char *pFullPath ) |
|
{ |
|
return PerformPerforceOp( "revert", pFullPath ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Helper for operations on multiple files |
|
//----------------------------------------------------------------------------- |
|
bool CP4::PerformPerforceOp( PerforceOp_t op, int nCount, const char **ppFullPathList, const char *pDescription ) |
|
{ |
|
g_ErrorHandlerUser.ResetErrorState(); |
|
|
|
if ( !IsConnectedToServer() ) |
|
{ |
|
g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL ); |
|
return false; |
|
} |
|
|
|
if ( nCount == 0 ) |
|
return true; |
|
|
|
char pOldClientSpec[MAX_PATH]; |
|
V_strncpy( pOldClientSpec, m_Client.GetClient().Text(), sizeof(pOldClientSpec) ); |
|
|
|
char pCurrentClientSpec[MAX_PATH]; |
|
V_strncpy( pCurrentClientSpec, pOldClientSpec, sizeof(pCurrentClientSpec) ); |
|
|
|
int nFirstIndex = 0; |
|
while ( nFirstIndex < nCount ) |
|
{ |
|
char pClientSpec[MAX_PATH]; |
|
|
|
bool bChangeSpec = false; |
|
int i; |
|
for ( i = nFirstIndex; i < nCount; ++i ) |
|
{ |
|
if ( s_p4.GetClientSpecForFile( ppFullPathList[i], pClientSpec, sizeof(pClientSpec) ) ) |
|
{ |
|
if ( V_stricmp( pCurrentClientSpec, pClientSpec ) ) |
|
{ |
|
bChangeSpec = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( i != nFirstIndex ) |
|
{ |
|
op( i - nFirstIndex, &ppFullPathList[nFirstIndex], pDescription ); |
|
nFirstIndex = i; |
|
} |
|
|
|
if ( bChangeSpec ) |
|
{ |
|
s_p4.SetActiveClient( pClientSpec ); |
|
V_strncpy( pCurrentClientSpec, pClientSpec, sizeof(pCurrentClientSpec) ); |
|
} |
|
} |
|
|
|
if ( V_stricmp( pCurrentClientSpec, pOldClientSpec ) ) |
|
{ |
|
s_p4.SetActiveClient( pOldClientSpec ); |
|
} |
|
|
|
return g_ErrorHandlerUser.GetErrorState() < E_WARN; |
|
} |
|
|
|
static const char *s_pOperation; |
|
void SimplePerforceOp( int nCount, const char **ppFullPathList, const char *pDescription ) |
|
{ |
|
s_p4.GetClientApi().SetArgv( nCount, const_cast<char**>( ppFullPathList ) ); |
|
s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser ); |
|
} |
|
|
|
bool CP4::PerformPerforceOp( const char *pOperation, int nCount, const char **ppFullPathList ) |
|
{ |
|
s_pOperation = pOperation; |
|
return PerformPerforceOp( SimplePerforceOp, nCount, ppFullPathList, NULL ); |
|
} |
|
|
|
void SimplePerforceOpCurChangeList( int nCount, const char **ppFullPathList, const char *pDescription ) |
|
{ |
|
if ( char const *szClNum = s_p4.GetOpenFileChangeListNum() ) |
|
{ |
|
CUtlVector< const char * > arrArgv; |
|
arrArgv.SetCount( nCount + 3 ); |
|
arrArgv[0] = "-c"; |
|
arrArgv[1] = szClNum; |
|
for ( int k = 0; k < nCount; ++ k ) |
|
arrArgv[ 2 + k ] = ppFullPathList[ k ]; |
|
arrArgv[ nCount + 2 ] = NULL; |
|
|
|
s_p4.GetClientApi().SetArgv( nCount + 2, const_cast<char**>( arrArgv.Base() ) ); |
|
s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser ); |
|
} |
|
else |
|
{ |
|
s_p4.GetClientApi().SetArgv( nCount, const_cast<char**>( ppFullPathList ) ); |
|
s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser ); |
|
} |
|
} |
|
|
|
bool CP4::PerformPerforceOpCurChangeList( const char *pOperation, int nCount, const char **ppFullPathList ) |
|
{ |
|
if ( m_sChangeListName.Length() ) |
|
{ |
|
s_pOperation = pOperation; |
|
return PerformPerforceOp( SimplePerforceOpCurChangeList, nCount, ppFullPathList, NULL ); |
|
} |
|
else |
|
{ |
|
return PerformPerforceOp( pOperation, nCount, ppFullPathList ); |
|
} |
|
} |
|
|
|
bool CP4::PerformPerforceOp( const char *pOperation, const char *pFullPath ) |
|
{ |
|
g_ErrorHandlerUser.ResetErrorState(); |
|
|
|
if ( !IsConnectedToServer() ) |
|
{ |
|
g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL ); |
|
return false; |
|
} |
|
|
|
CScopedFileClientSpec spec( pFullPath ); |
|
|
|
char *argv[] = { (char *)pFullPath, NULL }; |
|
m_Client.SetArgv( 1, argv ); |
|
m_Client.Run( pOperation, &g_ErrorHandlerUser ); |
|
|
|
return g_ErrorHandlerUser.GetErrorState() < E_WARN; |
|
} |
|
|
|
bool CP4::PerformPerforceOpCurChangeList( const char *pOperation, const char *pFullPath ) |
|
{ |
|
g_ErrorHandlerUser.ResetErrorState(); |
|
|
|
if ( !IsConnectedToServer() ) |
|
{ |
|
g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL ); |
|
return false; |
|
} |
|
|
|
CScopedFileClientSpec spec( pFullPath ); |
|
|
|
char *argv[] = { "-c", "", (char *)pFullPath, NULL }, **pArgv = argv; |
|
int numArgv = 3; |
|
|
|
if ( char const *szClNum = GetOpenFileChangeListNum() ) |
|
{ |
|
argv[ 1 ] = const_cast< char * >( szClNum ); |
|
} |
|
else |
|
{ |
|
pArgv += 2; |
|
numArgv -= 2; |
|
} |
|
|
|
m_Client.SetArgv( numArgv, pArgv ); |
|
m_Client.Run( pOperation, &g_ErrorHandlerUser ); |
|
|
|
return g_ErrorHandlerUser.GetErrorState() < E_WARN; |
|
} |
|
|
|
bool CP4::OpenFilesForAdd( int nCount, const char **ppFullPathList ) |
|
{ |
|
return PerformPerforceOpCurChangeList( "add", nCount, ppFullPathList ); |
|
} |
|
|
|
bool CP4::OpenFilesForEdit( int nCount, const char **ppFullPathList ) |
|
{ |
|
if ( !PerformPerforceOpCurChangeList( "edit", nCount, ppFullPathList ) ) |
|
return false; |
|
|
|
return MakeFilesWritable( nCount, ppFullPathList ); |
|
} |
|
|
|
bool CP4::OpenFilesForDelete( int nCount, const char **ppFullPathList ) |
|
{ |
|
return PerformPerforceOpCurChangeList( "delete", nCount, ppFullPathList ); |
|
} |
|
|
|
bool CP4::RevertFiles( int nCount, const char **ppFullPathList ) |
|
{ |
|
return PerformPerforceOp( "revert", nCount, ppFullPathList ); |
|
} |
|
|
|
|
|
void SubmitPerforceOp( int nCount, const char **ppFullPathList, const char *pDescription ) |
|
{ |
|
Assert( nCount > 0 ); |
|
if ( nCount <= 0 ) |
|
return; |
|
|
|
// First, create a new changelist |
|
g_ChangelistCreateUser.CreateChangelist( pDescription ); |
|
if ( g_ChangelistCreateUser.GetData().Count() == 0 ) |
|
{ |
|
g_ErrorHandlerUser.SetErrorString( "Failed to create changelist for submit operation", E_FATAL ); |
|
return; |
|
} |
|
|
|
int nChangeList = g_ChangelistCreateUser.GetData()[0]; |
|
|
|
// Next, move all files to that new changelist |
|
char pBuf[128]; |
|
V_snprintf( pBuf, sizeof(pBuf), "%d", nChangeList ); |
|
char **ppArgv = (char **)_alloca( (nCount + 2) * sizeof(char*) ); |
|
ppArgv[0] = "-c"; |
|
ppArgv[1] = pBuf; |
|
memcpy( &ppArgv[2], ppFullPathList, nCount * sizeof(char*) ); |
|
s_p4.GetClientApi().SetArgv( nCount+2, ppArgv ); |
|
s_p4.GetClientApi().Run( "reopen", &g_ErrorHandlerUser ); |
|
|
|
// Finally, submit that changelist |
|
g_SubmitUser.Submit( nChangeList ); |
|
} |
|
|
|
bool CP4::SubmitFiles( int nCount, const char **ppFullPathList, const char *pDescription ) |
|
{ |
|
return PerformPerforceOp( SubmitPerforceOp, nCount, ppFullPathList, pDescription ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Opens a file in s_p4 win |
|
//----------------------------------------------------------------------------- |
|
void CP4::OpenFileInP4Win( const char *pFullPath ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return; |
|
|
|
char pClientSpec[MAX_PATH]; |
|
char pSystem[512]; |
|
if ( GetClientSpecForFile( pFullPath, pClientSpec, sizeof(pClientSpec) ) ) |
|
{ |
|
V_snprintf( pSystem, sizeof(pSystem), "p4win -q -c %s -s %s", pClientSpec, pFullPath ); |
|
} |
|
else |
|
{ |
|
V_snprintf( pSystem, sizeof(pSystem), "p4win -q -s %s", pFullPath ); |
|
} |
|
|
|
STARTUPINFO si; |
|
PROCESS_INFORMATION pi; |
|
|
|
ZeroMemory( &si, sizeof(si) ); |
|
si.cb = sizeof(si); |
|
ZeroMemory( &pi, sizeof(pi) ); |
|
|
|
// Start the child process. |
|
CreateProcess( NULL, // No module name (use command line). |
|
pSystem, // Command line. |
|
NULL, // Process handle not inheritable. |
|
NULL, // Thread handle not inheritable. |
|
FALSE, // Set handle inheritance to FALSE. |
|
0, // No creation flags. |
|
NULL, // Use parent's environment block. |
|
NULL, // Use parent's starting directory. |
|
&si, // Pointer to STARTUPINFO structure. |
|
&pi ); // Pointer to PROCESS_INFORMATION structure. |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is this file in perforce? |
|
//----------------------------------------------------------------------------- |
|
bool CP4::IsFileInPerforce( const char *fullpath ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return false; |
|
|
|
CScopedFileClientSpec spec( fullpath ); |
|
|
|
g_FileUser.RetrieveFile( fullpath ); |
|
if ( g_FileUser.GetData().Count() == 0 ) |
|
return false; |
|
|
|
// Return deleted files as not being in perforce |
|
return !g_FileUser.GetData()[0].m_bDeleted; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is this file opened for edit? |
|
//----------------------------------------------------------------------------- |
|
P4FileState_t CP4::GetFileState( const char *pFullPath ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return P4FILE_UNOPENED; |
|
|
|
CScopedFileClientSpec spec( pFullPath ); |
|
|
|
g_FileUser.RetrieveOpenedFiles( pFullPath ); |
|
if ( g_FileUser.GetData().Count() == 0 ) |
|
return P4FILE_UNOPENED; |
|
return g_FileUser.GetData()[0].m_eOpenState; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns file information for a single file |
|
//----------------------------------------------------------------------------- |
|
bool CP4::GetFileInfo( const char *pFullPath, P4File_t *pFileInfo ) |
|
{ |
|
if ( !IsConnectedToServer() ) |
|
return false; |
|
|
|
CScopedFileClientSpec spec( pFullPath ); |
|
|
|
g_FileUser.RetrieveFile( pFullPath ); |
|
if ( g_FileUser.GetData().Count() != 1 ) |
|
return false; |
|
|
|
memcpy( pFileInfo, &g_FileUser.GetData()[0], sizeof(P4File_t) ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Are we connected to the server? (and should we reconnect?) |
|
//----------------------------------------------------------------------------- |
|
bool CP4::IsConnectedToServer( bool bRetry ) |
|
{ |
|
if ( bRetry && m_bConnectedToServer ) |
|
{ |
|
// don't comment this back in until it's called less often to avoid spamming the server |
|
// (currently, this is called every time the ifm file menu is opened) |
|
// this means that we won't detect losing the server until after a failed Run() cmd |
|
// -jd |
|
|
|
// m_Client.Run( "monitor show" ); // do something to test if the server's still up |
|
if ( m_Client.Dropped() ) |
|
{ |
|
Shutdown(); |
|
} |
|
} |
|
|
|
if ( !m_bConnectedToServer && bRetry ) |
|
{ |
|
Init(); |
|
} |
|
|
|
return m_bConnectedToServer; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// retrieves the last error from the last op (which is likely to span multiple lines) |
|
// this is only valid after OpenFile[s]For{Add,Edit,Delete} or {Submit,Revert}File[s] |
|
//----------------------------------------------------------------------------- |
|
const char *CP4::GetLastError() |
|
{ |
|
return g_ErrorHandlerUser.GetErrorString(); |
|
}
|
|
|