Team Fortress 2 Source Code as on 22/4/2020
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

  1. //====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. // prevent Error function from being defined, since it inteferes with the P4 API
  7. #define Error Warning
  8. #include "p4lib/ip4.h"
  9. #include "tier1/utlvector.h"
  10. #include "tier1/utlsymbol.h"
  11. #include "tier1/utlbuffer.h"
  12. #include "tier1/utlstring.h"
  13. #include "tier1/utlmap.h"
  14. #include "tier1/characterset.h"
  15. #include "filesystem.h"
  16. #undef Error
  17. #undef Verify
  18. #include "clientapi.h"
  19. #include <time.h>
  20. #include <ctype.h>
  21. #include <io.h>
  22. #include <sys/stat.h>
  23. #include <windows.h>
  24. #define CLIENTSPEC_BUFFER_SIZE (16 * 1024)
  25. //-----------------------------------------------------------------------------
  26. // Stores info about the client path
  27. //-----------------------------------------------------------------------------
  28. struct CClientPathRecord
  29. {
  30. char m_szDepotPath[MAX_PATH];
  31. char m_szClientPath[MAX_PATH];
  32. bool m_bNegative;
  33. };
  34. //-----------------------------------------------------------------------------
  35. // Global interfaces
  36. //-----------------------------------------------------------------------------
  37. #if defined(STANDALONE_VPC)
  38. #include "../utils/vpc/sys_utils.h"
  39. #define FileExists( arg ) Sys_Exists( arg )
  40. #define ReadFile( path, unused, buf ) Sys_LoadFileIntoBuffer( path, buf, false )
  41. #else
  42. static IFileSystem *g_pFileSystem;
  43. #define FileExists( arg ) g_pFileSystem->FileExists( arg )
  44. #define ReadFile( path, unused, buf ) g_pFilesystem->ReadFile( path, unused, buf )
  45. #endif
  46. //-----------------------------------------------------------------------------
  47. // Purpose: Interface to accessing P4 commands
  48. //-----------------------------------------------------------------------------
  49. class CP4 : public CBaseAppSystem<IP4>
  50. {
  51. public:
  52. // Destructor
  53. virtual ~CP4();
  54. // Methods of IAppSystem
  55. virtual bool Connect( CreateInterfaceFn factory );
  56. virtual InitReturnVal_t Init();
  57. virtual void *QueryInterface( const char *pInterfaceName );
  58. virtual void Shutdown();
  59. virtual void Disconnect();
  60. // Inherited from IP4
  61. P4Client_t &GetActiveClient();
  62. virtual void RefreshActiveClient();
  63. virtual void SetActiveClient(const char *clientname);
  64. virtual void GetDepotFilePath(char *depotFilePath, const char *filespec, int size);
  65. virtual void GetClientFilePath(char *clientFilePath, const char *filespec, int size);
  66. virtual void GetLocalFilePath(char *localFilePath, const char *filespec, int size);
  67. virtual CUtlVector<P4File_t> &GetFileList(const char *path);
  68. virtual CUtlVector<P4File_t> &GetFileListUsingClientSpec( const char *pPath, const char *pClientSpec );
  69. virtual void GetOpenedFileList( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly );
  70. virtual void GetOpenedFileList( const char *pRootDirectory, CUtlVector<P4File_t> &fileList );
  71. virtual void GetOpenedFileListInPath( const char *pPathID, CUtlVector<P4File_t> &fileList );
  72. virtual void GetFileListInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList );
  73. virtual CUtlVector<P4Revision_t> &GetRevisionList(const char *path, bool bIsDir);
  74. virtual CUtlVector<P4Client_t> &GetClientList();
  75. virtual void RemovePathFromActiveClientspec(const char *path);
  76. virtual void SetOpenFileChangeList( const char *pChangeListName );
  77. virtual bool OpenFileForAdd( const char *fullpath );
  78. virtual bool OpenFileForEdit(const char *fullpath);
  79. virtual bool OpenFileForDelete(const char *fullpath);
  80. virtual bool SyncFile( const char *pFullPath, int nRevision = -1 );
  81. virtual bool SubmitFile( const char *pFullPath, const char *pDescription );
  82. virtual bool RevertFile( const char *pFullPath );
  83. virtual bool OpenFilesForAdd( int nCount, const char **ppFullPathList );
  84. virtual bool OpenFilesForEdit( int nCount, const char **ppFullPathList );
  85. virtual bool OpenFilesForDelete( int nCount, const char **ppFullPathList );
  86. virtual bool SubmitFiles( int nCount, const char **ppFullPathList, const char *pDescription );
  87. virtual bool RevertFiles( int nCount, const char **ppFullPathList );
  88. virtual bool IsFileInPerforce( const char *fullpath );
  89. virtual P4FileState_t GetFileState( const char *pFullPath );
  90. virtual bool GetFileInfo( const char *pFullPath, P4File_t *pFileInfo );
  91. virtual const char *GetDepotRoot();
  92. virtual int GetDepotRootLength();
  93. virtual const char *GetLocalRoot();
  94. virtual int GetLocalRootLength();
  95. virtual const char *String( CUtlSymbol s ) const;
  96. virtual bool GetClientSpecForFile( const char *pFullPath, char *pClientSpec, int nMaxLen );
  97. virtual bool GetClientSpecForDirectory( const char *pFullPathDir, char *pClientSpec, int nMaxLen );
  98. virtual bool GetClientSpecForPath( const char *pPathId, char *pClientSpec, int nMaxLen );
  99. virtual void OpenFileInP4Win( const char *pFullPath );
  100. virtual bool IsConnectedToServer( bool bRetry = true );
  101. virtual const char *GetLastError();
  102. char const * GetOpenFileChangeListNum(); // Returns NULL for default
  103. // Convert a depot file to a local file user the current client mapping
  104. bool DepotFileToLocalFile( const char *pDepotFile, char *pLocalFile, int nBufLen );
  105. // Accessors
  106. ClientApi &GetClientApi() { return m_Client; }
  107. ClientUser &GetClientUser() { return m_User; }
  108. private:
  109. // Helper for operations on multiple files
  110. typedef void (*PerforceOp_t)( int nCount, const char **ppFullPathList, const char *pDescription );
  111. bool PerformPerforceOp( PerforceOp_t op, int nCount, const char **ppFullPathList, const char *pDescription );
  112. bool PerformPerforceOp( const char *pOperation, int nCount, const char **ppFullPathList );
  113. bool PerformPerforceOp( const char *pOperation, const char *pFullPath );
  114. bool PerformPerforceOpCurChangeList( const char *pOperation, int nCount, const char **ppFullPathList );
  115. bool PerformPerforceOpCurChangeList( const char *pOperation, const char *pFullPath );
  116. void RefreshClientData();
  117. bool m_bConnectedToServer;
  118. P4Client_t m_ActiveClient;
  119. CUtlVector< CClientPathRecord > m_ClientMapping;
  120. char m_szDepotRoot[_MAX_PATH];
  121. char m_szLocalRoot[_MAX_PATH];
  122. int m_iDepotRootLength;
  123. int m_iLocalRootLength;
  124. CUtlString m_sChangeListName, m_sCachedChangeListNum;
  125. int m_nCachedChangeListNumber;
  126. // internal
  127. ClientApi m_Client;
  128. ClientUser m_User;
  129. };
  130. //-----------------------------------------------------------------------------
  131. // global instance
  132. //-----------------------------------------------------------------------------
  133. CP4 s_p4;
  134. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CP4, IP4, P4_INTERFACE_VERSION, s_p4 );
  135. //-----------------------------------------------------------------------------
  136. // Scoped class to push and pop a particular client spec
  137. //-----------------------------------------------------------------------------
  138. class CScopedClientSpec
  139. {
  140. public:
  141. CScopedClientSpec( const char *pClientSpec );
  142. ~CScopedClientSpec();
  143. private:
  144. char m_pOldClientSpec[MAX_PATH];
  145. bool m_bClientSpecNeedsRestore;
  146. };
  147. CScopedClientSpec::CScopedClientSpec( const char *pClientSpec )
  148. {
  149. m_bClientSpecNeedsRestore = false;
  150. V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
  151. if ( V_stricmp( m_pOldClientSpec, pClientSpec ) )
  152. {
  153. s_p4.SetActiveClient( pClientSpec );
  154. m_bClientSpecNeedsRestore = true;
  155. }
  156. }
  157. CScopedClientSpec::~CScopedClientSpec()
  158. {
  159. if ( m_bClientSpecNeedsRestore )
  160. {
  161. s_p4.SetActiveClient( m_pOldClientSpec );
  162. }
  163. }
  164. class CScopedFileClientSpec
  165. {
  166. public:
  167. CScopedFileClientSpec( const char *pFullPath );
  168. ~CScopedFileClientSpec();
  169. private:
  170. char m_pOldClientSpec[MAX_PATH];
  171. bool m_bClientSpecNeedsRestore;
  172. };
  173. CScopedFileClientSpec::CScopedFileClientSpec( const char *pFullPath )
  174. {
  175. m_bClientSpecNeedsRestore = false;
  176. char pClientSpec[MAX_PATH];
  177. if ( s_p4.GetClientSpecForFile( pFullPath, pClientSpec, sizeof(pClientSpec) ) )
  178. {
  179. V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
  180. if ( V_stricmp( m_pOldClientSpec, pClientSpec ) )
  181. {
  182. s_p4.SetActiveClient( pClientSpec );
  183. m_bClientSpecNeedsRestore = true;
  184. }
  185. }
  186. }
  187. CScopedFileClientSpec::~CScopedFileClientSpec()
  188. {
  189. if ( m_bClientSpecNeedsRestore )
  190. {
  191. s_p4.SetActiveClient( m_pOldClientSpec );
  192. }
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Scoped class to push and pop a particular client spec
  196. //-----------------------------------------------------------------------------
  197. class CScopedDirClientSpec
  198. {
  199. public:
  200. CScopedDirClientSpec( const char *pFullPathDir );
  201. ~CScopedDirClientSpec();
  202. private:
  203. char m_pOldClientSpec[MAX_PATH];
  204. bool m_bClientSpecNeedsRestore;
  205. };
  206. CScopedDirClientSpec::CScopedDirClientSpec( const char *pFullPathDir )
  207. {
  208. m_bClientSpecNeedsRestore = false;
  209. char pClientSpec[MAX_PATH];
  210. if ( s_p4.GetClientSpecForDirectory( pFullPathDir, pClientSpec, sizeof(pClientSpec) ) )
  211. {
  212. V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
  213. if ( V_stricmp( m_pOldClientSpec, pClientSpec ) )
  214. {
  215. s_p4.SetActiveClient( pClientSpec );
  216. m_bClientSpecNeedsRestore = true;
  217. }
  218. }
  219. }
  220. CScopedDirClientSpec::~CScopedDirClientSpec()
  221. {
  222. if ( m_bClientSpecNeedsRestore )
  223. {
  224. s_p4.SetActiveClient( m_pOldClientSpec );
  225. }
  226. }
  227. //-----------------------------------------------------------------------------
  228. // Scoped class to push and pop a particular client spec
  229. //-----------------------------------------------------------------------------
  230. class CScopedPathClientSpec
  231. {
  232. public:
  233. CScopedPathClientSpec( const char *pPathID );
  234. ~CScopedPathClientSpec();
  235. private:
  236. char m_pOldClientSpec[MAX_PATH];
  237. bool m_bClientSpecNeedsRestore;
  238. };
  239. CScopedPathClientSpec::CScopedPathClientSpec( const char *pPathID )
  240. {
  241. m_bClientSpecNeedsRestore = false;
  242. char pClientSpec[MAX_PATH];
  243. if ( s_p4.GetClientSpecForPath( pPathID, pClientSpec, sizeof(pClientSpec) ) )
  244. {
  245. V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
  246. s_p4.SetActiveClient( pClientSpec );
  247. m_bClientSpecNeedsRestore = true;
  248. }
  249. }
  250. CScopedPathClientSpec::~CScopedPathClientSpec()
  251. {
  252. if ( m_bClientSpecNeedsRestore )
  253. {
  254. s_p4.SetActiveClient( m_pOldClientSpec );
  255. }
  256. }
  257. //-----------------------------------------------------------------------------
  258. // Purpose: utility function to split a typical P4 line output into var and value
  259. //-----------------------------------------------------------------------------
  260. static void SplitP4Output(const char *data, char *pszCmd, char *pszInfo, int bufLen)
  261. {
  262. V_strncpy(pszCmd, data, bufLen);
  263. char *mid = V_strstr(pszCmd, " ");
  264. if (mid)
  265. {
  266. *mid = 0;
  267. V_strncpy(pszInfo, data + (mid - pszCmd) + 1, bufLen);
  268. }
  269. else
  270. {
  271. pszInfo[0] = 0;
  272. }
  273. }
  274. //-----------------------------------------------------------------------------
  275. // Purpose: base class for parse input from the P4 server
  276. //-----------------------------------------------------------------------------
  277. template< class T >
  278. class CDataRetrievalUser : public ClientUser
  279. {
  280. public:
  281. CUtlVector<T> &GetData()
  282. {
  283. return m_Data;
  284. }
  285. // call this to start retrieving data
  286. void InitRetrievingData()
  287. {
  288. m_bAwaitingNewRecord = true;
  289. m_pData = &m_Data;
  290. m_pData->RemoveAll();
  291. }
  292. // call this to start retrieving data into an external piece of memory
  293. void InitRetrievingData( CUtlVector<T> *pData )
  294. {
  295. m_bAwaitingNewRecord = true;
  296. m_pData = pData;
  297. m_pData->RemoveAll();
  298. }
  299. T *ForceNextRecord()
  300. {
  301. int index = m_pData->AddToTail();
  302. if ( m_pData->Count() > 1 )
  303. {
  304. m_pData->Element(index) = m_pData->Element(0);
  305. }
  306. return &m_pData->Element(index);
  307. }
  308. // implement this to parse out input from the server into the specified object
  309. virtual void OutputRecord(T &obj, const char *pszVar, const char *pszInfo) = 0;
  310. private:
  311. bool m_bAwaitingNewRecord;
  312. CUtlVector<T> m_Data;
  313. CUtlVector<T> *m_pData;
  314. virtual void OutputInfo( char level, const char *data )
  315. {
  316. if ( V_strlen(data) < 1 )
  317. {
  318. // end of a record, await the new one
  319. m_bAwaitingNewRecord = true;
  320. return;
  321. }
  322. if ( m_bAwaitingNewRecord )
  323. {
  324. // add in the new record
  325. T &record = m_pData->Element( m_pData->AddToTail() );
  326. memset( &record, 0, sizeof( record ) );
  327. Construct( &record );
  328. m_bAwaitingNewRecord = false;
  329. }
  330. // parse
  331. char szVar[_MAX_PATH];
  332. char szInfo[_MAX_PATH];
  333. SplitP4Output( data, szVar, szInfo, sizeof( szVar ) );
  334. // emit
  335. T &record = m_pData->Element( m_pData->Count() - 1 );
  336. OutputRecord( record, szVar, szInfo );
  337. }
  338. };
  339. //-----------------------------------------------------------------------------
  340. // Purpose: Retrieves a file list
  341. //-----------------------------------------------------------------------------
  342. class CFileUser : public CDataRetrievalUser<P4File_t>
  343. {
  344. public:
  345. void RetrieveDir(const char *dir)
  346. {
  347. // clear the list
  348. InitRetrievingData();
  349. // search for the paths - this string gets all revisions of a file,
  350. // from the version they have locally to the current version
  351. // this is so we can see if the local files are out of date
  352. char szSearch[MAX_PATH];
  353. V_snprintf(szSearch, sizeof(szSearch), "%s/*", dir);
  354. // gets directories first
  355. char *argv[] = { "-C", (char *)szSearch, NULL };
  356. s_p4.GetClientApi().SetArgv( 2, argv );
  357. s_p4.GetClientApi().Run("dirs", this);
  358. // now get files, '-Rc' restricts files to client mapping
  359. char *argv2[] = { "-Rc", "-Op", (char *)szSearch, NULL };
  360. s_p4.GetClientApi().SetArgv( 3, argv2 );
  361. s_p4.GetClientApi().Run("fstat", this);
  362. ComputeLocalDirectoryNames( GetData() );
  363. }
  364. void RetrieveFile(const char *filespec)
  365. {
  366. // clear the list
  367. InitRetrievingData();
  368. // now get files, '-Rc' restricts files to client mapping
  369. char *argv2[] = { "-Rc", "-Op", (char *)filespec, NULL };
  370. s_p4.GetClientApi().SetArgv( 3, argv2 );
  371. s_p4.GetClientApi().Run("fstat", this);
  372. ComputeLocalDirectoryNames( GetData() );
  373. }
  374. // Show how file names map through the client view
  375. // For each file in filespec, three names are produced:
  376. // depotFile the name in the depot
  377. // clientFile the name on the client in Perforce syntax
  378. // localFile the name on the client in local syntax
  379. void RetrieveWhereabouts(const char *filespec)
  380. {
  381. // clear the list
  382. InitRetrievingData();
  383. // figure out where filespec is
  384. char *argv[] = { (char *)filespec, NULL };
  385. s_p4.GetClientApi().SetArgv( 1, argv );
  386. s_p4.GetClientApi().Run("where", this);
  387. }
  388. void RetrieveOpenedFiles( const char *filespec )
  389. {
  390. // clear the list
  391. InitRetrievingData();
  392. char *argv[] = { (char *)filespec, NULL };
  393. s_p4.GetClientApi().SetArgv( 1, argv );
  394. s_p4.GetClientApi().Run("opened", this);
  395. ComputeLocalFileNames( GetData() );
  396. }
  397. void BeginChangelistProcess()
  398. {
  399. m_bChangesRecord = true;
  400. m_nChangesIndex = 0;
  401. }
  402. void EndChangelistProcess()
  403. {
  404. m_bChangesRecord = false;
  405. m_nChangesIndex = -1;
  406. }
  407. void RetrieveOpenedFiles( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly )
  408. {
  409. // clear the list
  410. InitRetrievingData( &fileList );
  411. if ( bDefaultChangeOnly )
  412. {
  413. char *argv[] = { (char *)"-c", (char*)"default", NULL };
  414. s_p4.GetClientApi().SetArgv( 2, argv );
  415. }
  416. s_p4.GetClientApi().Run( "opened", this );
  417. ComputeLocalFileNames( fileList );
  418. }
  419. void RetrieveFilesInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList )
  420. {
  421. BeginChangelistProcess();
  422. InitRetrievingData( &fileList );
  423. char changeListString[32];
  424. V_snprintf( changeListString, sizeof(changeListString), "%d", changeListNumber );
  425. char *argv[] = { (char *)"-s", changeListString, NULL };
  426. s_p4.GetClientApi().SetArgv( 2, argv );
  427. s_p4.GetClientApi().Run( "describe", this );
  428. EndChangelistProcess();
  429. }
  430. private:
  431. void ComputeLocalFileNames( CUtlVector<P4File_t> &fileList )
  432. {
  433. // Need to construct valid local paths since opened doesn't do it for us
  434. char pMatchPattern[MAX_PATH];
  435. V_snprintf( pMatchPattern, sizeof(pMatchPattern), "//%s/", s_p4.GetClientApi().GetClient().Text() );
  436. int nLen = V_strlen( pMatchPattern );
  437. int nCount = fileList.Count();
  438. for ( int i = 0; i < nCount; ++i )
  439. {
  440. if ( fileList[i].m_bDir )
  441. continue;
  442. const char *pClientPath = s_p4.String( fileList[i].m_sClientFile );
  443. if ( V_stristr( pClientPath, pMatchPattern ) != pClientPath )
  444. continue;
  445. char pLocalPath[MAX_PATH];
  446. V_ComposeFileName( s_p4.GetLocalRoot(), pClientPath + nLen, pLocalPath, sizeof(pLocalPath) );
  447. V_FixSlashes( pLocalPath );
  448. fileList[i].m_sLocalFile = pLocalPath;
  449. }
  450. }
  451. void ComputeLocalDirectoryNames( CUtlVector<P4File_t> &fileList )
  452. {
  453. // Need to construct valid local paths since opened doesn't do it for us
  454. int nCount = fileList.Count();
  455. for ( int i = 0; i < nCount; ++i )
  456. {
  457. if ( !fileList[i].m_bDir )
  458. continue;
  459. char pLocalPath[MAX_PATH];
  460. const char *pDepotPath = s_p4.String( fileList[i].m_sDepotFile );
  461. if ( !s_p4.DepotFileToLocalFile( pDepotPath, pLocalPath, sizeof(pLocalPath) ) )
  462. continue;
  463. fileList[i].m_sLocalFile = pLocalPath;
  464. }
  465. }
  466. void OutputRecordInternal(P4File_t &file, const char *szCmd, const char *szInfo)
  467. {
  468. if (!V_strcmp(szCmd, "headRev"))
  469. {
  470. file.m_iHeadRevision = atoi(szInfo);
  471. }
  472. else if (!V_strcmp(szCmd, "haveRev"))
  473. {
  474. file.m_iHaveRevision = atoi(szInfo);
  475. }
  476. else if (!V_strcmp(szCmd, "headAction") && !V_strcmp(szInfo, "delete"))
  477. {
  478. file.m_bDeleted = true;
  479. }
  480. else if (!V_strcmp(szCmd, "action"))
  481. {
  482. if (!V_strcmp(szInfo, "edit"))
  483. {
  484. file.m_eOpenState = P4FILE_OPENED_FOR_EDIT;
  485. }
  486. else if (!V_strcmp(szInfo, "delete"))
  487. {
  488. file.m_eOpenState = P4FILE_OPENED_FOR_DELETE;
  489. }
  490. else if (!V_strcmp(szInfo, "add"))
  491. {
  492. file.m_eOpenState = P4FILE_OPENED_FOR_ADD;
  493. }
  494. else if (!V_strcmp(szInfo, "integrate"))
  495. {
  496. file.m_eOpenState = P4FILE_OPENED_FOR_INTEGRATE;
  497. }
  498. }
  499. else if (!V_strcmp(szCmd, "change"))
  500. {
  501. file.m_iChangelist = atoi(szInfo);
  502. }
  503. else if ( !V_strcmp( szCmd, "otherOpen" ) )
  504. {
  505. file.m_bOpenedByOther = atoi( szInfo ) != 0;
  506. }
  507. else if ( m_bChangesRecord && (!V_strcmp(szCmd, "user") || !V_strcmp(szCmd,"client") || !V_strcmp(szCmd, "time") ||
  508. !V_strcmp(szCmd, "desc") || !V_strcmp(szCmd, "status") || !V_strcmp(szCmd, "oldChange") ||
  509. !V_strcmp(szCmd,"type") || !V_strcmp(szCmd,"rev")) )
  510. {
  511. // ignore these, processing changelist description
  512. }
  513. else
  514. {
  515. // extract the path
  516. char pFilePath[_MAX_PATH];
  517. V_strncpy( pFilePath, szInfo, sizeof( pFilePath ) );
  518. char *pFileName = V_strrchr(pFilePath, '/');
  519. if (pFileName)
  520. {
  521. ++pFileName;
  522. }
  523. if ( !V_stricmp( szCmd, "dir" ) )
  524. {
  525. // new directory, add to the list
  526. file.m_sPath = pFilePath;
  527. file.m_sDepotFile = pFilePath;
  528. file.m_sName = pFileName;
  529. file.m_bDir = true;
  530. }
  531. else if ( !V_strcmp( szCmd, "depotFile" ) )
  532. {
  533. if ( m_bChangesRecord )
  534. {
  535. char pLocalPath[MAX_PATH];
  536. if ( s_p4.DepotFileToLocalFile( szInfo, pLocalPath, sizeof(pLocalPath) ) )
  537. {
  538. file.m_sLocalFile = pLocalPath;
  539. }
  540. }
  541. char *pCruft = V_strstr( pFilePath, "//%%1" );
  542. if (pCruft)
  543. *pCruft = 0;
  544. file.m_sDepotFile = pFilePath;
  545. // Now that we've stored off the depot file, split file into path + name
  546. if (pFileName)
  547. {
  548. *(pFileName - 1) = 0;
  549. }
  550. file.m_sPath = pFilePath;
  551. file.m_sName = pFileName;
  552. file.m_bDir = false;
  553. }
  554. else if (!V_stricmp(szCmd, "clientFile"))
  555. {
  556. char *pCruft = V_strstr( pFilePath, "//%%1" );
  557. if (pCruft)
  558. *pCruft = 0;
  559. file.m_sClientFile = pFilePath;
  560. }
  561. else if (!V_stricmp(szCmd, "path"))
  562. {
  563. char *pCruft = V_strstr(pFilePath, "\\%%1");
  564. if (pCruft)
  565. *pCruft = 0;
  566. file.m_sLocalFile = pFilePath;
  567. }
  568. }
  569. }
  570. virtual void OutputRecord(P4File_t &file, const char *szCmd, const char *szInfo)
  571. {
  572. P4File_t *pFile = &file;
  573. if ( m_bChangesRecord )
  574. {
  575. char tmpCmd[1024];
  576. int end = V_strlen(szCmd)-1;
  577. if ( end < ARRAYSIZE(tmpCmd) )
  578. {
  579. const char *pChar = szCmd + end;
  580. while ( isdigit(*pChar) && pChar > szCmd )
  581. {
  582. pChar--;
  583. }
  584. int pos = pChar - szCmd;
  585. if ( pos > 0 && pos < end )
  586. {
  587. int newChangeIndex = atoi(pChar+1);
  588. V_strncpy( tmpCmd, szCmd, sizeof(tmpCmd) );
  589. tmpCmd[pos+1] = 0;
  590. szCmd = tmpCmd;
  591. if ( newChangeIndex != m_nChangesIndex )
  592. {
  593. pFile = ForceNextRecord();
  594. m_nChangesIndex = newChangeIndex;
  595. }
  596. }
  597. }
  598. }
  599. OutputRecordInternal( *pFile, szCmd, szInfo );
  600. }
  601. int m_nChangesIndex;
  602. bool m_bChangesRecord;
  603. };
  604. CFileUser g_FileUser;
  605. CFileUser g_WhereUser;
  606. //-----------------------------------------------------------------------------
  607. // Purpose: Retrieves a history list
  608. //-----------------------------------------------------------------------------
  609. class CRevisionHistoryUser : public CDataRetrievalUser<P4Revision_t>
  610. {
  611. public:
  612. void RetrieveHistory(const char *path, bool bDir)
  613. {
  614. InitRetrievingData();
  615. // search for the paths - this string gets all revisions of a file,
  616. // from the version they have locally to the current version
  617. // this is so we can see if the local files are out of date
  618. char szSearch[MAX_PATH];
  619. if (bDir)
  620. {
  621. V_snprintf(szSearch, sizeof(szSearch), "%s/...", path);
  622. }
  623. else
  624. {
  625. V_snprintf(szSearch, sizeof(szSearch), "%s", path);
  626. }
  627. // set to view long output, to show the time, and to have a maximum number of results
  628. char *argv[] = { "-l", "-t", "-m", "50", (char *)szSearch, NULL };
  629. s_p4.GetClientApi().SetArgv( 5, argv );
  630. s_p4.GetClientApi().Run("changes", this);
  631. }
  632. private:
  633. virtual void OutputRecord(P4Revision_t &revision, const char *szCmd, const char *szInfo)
  634. {
  635. if (!V_strcmp(szCmd, "change"))
  636. {
  637. revision.m_iChange = atoi(szInfo);
  638. }
  639. else if (!V_strcmp(szCmd, "user"))
  640. {
  641. revision.m_sUser = szInfo;
  642. }
  643. else if (!V_strcmp(szCmd, "client"))
  644. {
  645. revision.m_sClient = szInfo;
  646. }
  647. else if (!V_strcmp(szCmd, "status"))
  648. {
  649. }
  650. else if (!V_strcmp(szCmd, "desc"))
  651. {
  652. revision.m_Description = szInfo;
  653. }
  654. else if (!V_strcmp(szCmd, "time"))
  655. {
  656. int64 iTime = atoi(szInfo);
  657. struct tm *gmt = gmtime(reinterpret_cast<time_t*>(&iTime));
  658. revision.m_nYear = gmt->tm_year + 1900;
  659. revision.m_nMonth = gmt->tm_mon + 1;
  660. revision.m_nDay = gmt->tm_mday;
  661. revision.m_nHour = gmt->tm_hour;
  662. revision.m_nMinute = gmt->tm_min;
  663. revision.m_nSecond = gmt->tm_sec;
  664. }
  665. }
  666. };
  667. CRevisionHistoryUser g_RevisionHistoryUser;
  668. //-----------------------------------------------------------------------------
  669. // Purpose: Retreives list of clients
  670. //-----------------------------------------------------------------------------
  671. class CClientSpecUser : public CDataRetrievalUser<P4Client_t>
  672. {
  673. public:
  674. void RetrieveClients()
  675. {
  676. InitRetrievingData();
  677. // we just get the entire list
  678. s_p4.GetClientApi().Run("clients", this);
  679. }
  680. private:
  681. virtual void OutputRecord(P4Client_t &client, const char *szCmd, const char *szInfo)
  682. {
  683. if (!V_strcmp(szCmd, "client"))
  684. {
  685. client.m_sName = szInfo;
  686. }
  687. else if (!V_strcmp(szCmd, "Owner"))
  688. {
  689. client.m_sUser = szInfo;
  690. }
  691. else if (!V_strcmp(szCmd, "Root"))
  692. {
  693. client.m_sLocalRoot = szInfo;
  694. }
  695. else if (!V_strcmp(szCmd, "Host"))
  696. {
  697. client.m_sHost = szInfo;
  698. }
  699. else
  700. {
  701. Msg("Unknown field %s = %s\n", szCmd, szInfo);
  702. }
  703. }
  704. };
  705. CClientSpecUser g_ClientspecUser;
  706. //-----------------------------------------------------------------------------
  707. // Purpose: holder and modifier of clientspec file mapings
  708. //-----------------------------------------------------------------------------
  709. class CClientspecMap
  710. {
  711. public:
  712. char m_szFullClientspec[CLIENTSPEC_BUFFER_SIZE];
  713. int m_iFullClientspecWritePosition;
  714. CUtlVector<CClientPathRecord> m_PathMap;
  715. char m_szLocalPath[_MAX_PATH];
  716. void ResetFullClientSpec( void )
  717. {
  718. m_szFullClientspec[0] = '\0';
  719. m_iFullClientspecWritePosition = 0;
  720. m_PathMap.RemoveAll();
  721. m_szLocalPath[0] = '\0';
  722. }
  723. void AddVarToFullClientSpec( const char *variable, const char *data )
  724. {
  725. V_snprintf( &m_szFullClientspec[m_iFullClientspecWritePosition], CLIENTSPEC_BUFFER_SIZE - m_iFullClientspecWritePosition, "%s: %s\n\n", variable, data );
  726. m_iFullClientspecWritePosition += strlen( &m_szFullClientspec[m_iFullClientspecWritePosition] );
  727. }
  728. void AddStringToFullClientSpec( const char *szString )
  729. {
  730. V_strncpy( &m_szFullClientspec[m_iFullClientspecWritePosition], szString, CLIENTSPEC_BUFFER_SIZE - m_iFullClientspecWritePosition );
  731. m_iFullClientspecWritePosition += strlen( &m_szFullClientspec[m_iFullClientspecWritePosition] );
  732. }
  733. void ReadRoot( const char *clientRoot )
  734. {
  735. V_strncpy( m_szLocalPath, clientRoot, _MAX_PATH );
  736. }
  737. void ReadViewLine( const char *viewLine )
  738. {
  739. CUtlBuffer parse(viewLine, V_strlen(viewLine), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
  740. CClientPathRecord record;
  741. record.m_bNegative = false;
  742. // get the depot path
  743. parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
  744. if (V_strlen(record.m_szDepotPath) < 1)
  745. return;
  746. if (!V_stricmp(record.m_szDepotPath, "-"))
  747. {
  748. // it's the negation sign, get the next line
  749. parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
  750. record.m_bNegative = true;
  751. }
  752. if (record.m_szDepotPath[0] == '-')
  753. {
  754. // negation sign is stuck on the end, remove
  755. record.m_bNegative = true;
  756. memmove(record.m_szDepotPath, record.m_szDepotPath + 1, sizeof(record.m_szDepotPath) - 1);
  757. }
  758. // get the next sign
  759. parse.GetString(record.m_szClientPath, sizeof(record.m_szClientPath));
  760. // append to list
  761. m_PathMap.AddToTail(record);
  762. }
  763. void ReadClientspec(const char *clientspec)
  764. {
  765. // Get the local path
  766. char *pszSearch = "\n\nRoot:";
  767. char *pStr = V_strstr( clientspec, pszSearch );
  768. if (pStr)
  769. {
  770. pStr += V_strlen( pszSearch );
  771. // parse out next satring
  772. CUtlBuffer parse(pStr, V_strlen(pStr), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
  773. // get the local path
  774. parse.GetString( m_szLocalPath, sizeof(m_szLocalPath) );
  775. }
  776. pszSearch = "\n\nView:\n";
  777. pStr = V_strstr(clientspec, pszSearch);
  778. if (pStr)
  779. {
  780. pStr += V_strlen(pszSearch);
  781. // take a full copy of the string
  782. V_strncpy(m_szFullClientspec, clientspec, sizeof(m_szFullClientspec));
  783. // parse out strings
  784. CUtlBuffer parse(pStr, V_strlen(pStr), CUtlBuffer::TEXT_BUFFER|CUtlBuffer::READ_ONLY);
  785. while (parse.IsValid())
  786. {
  787. CClientPathRecord record;
  788. record.m_bNegative = false;
  789. // get the depot path
  790. parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
  791. if (V_strlen(record.m_szDepotPath) < 1)
  792. break;
  793. if (!V_stricmp(record.m_szDepotPath, "-"))
  794. {
  795. // it's the negation sign, get the next line
  796. parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
  797. record.m_bNegative = true;
  798. }
  799. if (record.m_szDepotPath[0] == '-')
  800. {
  801. // negation sign is stuck on the end, remove
  802. record.m_bNegative = true;
  803. memmove(record.m_szDepotPath, record.m_szDepotPath + 1, sizeof(record.m_szDepotPath) - 1);
  804. }
  805. // get the next sign
  806. parse.GetString(record.m_szClientPath, sizeof(record.m_szClientPath));
  807. // append to list
  808. m_PathMap.AddToTail(record);
  809. }
  810. }
  811. }
  812. void WriteClientspec(char *pszDest, int destSize)
  813. {
  814. // assemble the new view
  815. char szView[CLIENTSPEC_BUFFER_SIZE] = { 0 };
  816. for (int i = 0; i < m_PathMap.Count(); i++)
  817. {
  818. CClientPathRecord &record = m_PathMap[i];
  819. V_strncat(szView, " ", sizeof(szView), COPY_ALL_CHARACTERS);
  820. if (record.m_bNegative)
  821. {
  822. V_strncat(szView, "-", sizeof(szView), COPY_ALL_CHARACTERS);
  823. }
  824. V_strncat(szView, record.m_szDepotPath, sizeof(szView), COPY_ALL_CHARACTERS);
  825. V_strncat(szView, " ", sizeof(szView), COPY_ALL_CHARACTERS);
  826. V_strncat(szView, record.m_szClientPath, sizeof(szView), COPY_ALL_CHARACTERS);
  827. V_strncat(szView, " \n", sizeof(szView), COPY_ALL_CHARACTERS);
  828. }
  829. // find the place the view is set
  830. char *pszSearch = "\n\nView:\n";
  831. char *pStr = V_strstr(m_szFullClientspec, pszSearch);
  832. if (pStr)
  833. {
  834. char *pDest = (pStr + strlen(pszSearch));
  835. // replace the view with the new view
  836. V_strncpy(pDest, szView, sizeof(m_szFullClientspec) - (pDest - m_szFullClientspec));
  837. }
  838. // emit
  839. V_strncpy(pszDest, m_szFullClientspec, destSize);
  840. }
  841. void RemovePathFromClient(const char *path)
  842. {
  843. // work out how the path record contains the specified path
  844. for (int i = 0; i < m_PathMap.Count(); i++)
  845. {
  846. // look for the first path that matches, comparing the two strings up to before the "/..." in szDepotPath
  847. if (!V_strnicmp(m_PathMap[i].m_szDepotPath, path, V_strlen(m_PathMap[i].m_szDepotPath) - 4))
  848. {
  849. CClientPathRecord &baseRecord = m_PathMap[i];
  850. // we have a match, build the negative case
  851. CClientPathRecord negativeRecord;
  852. negativeRecord.m_bNegative = true;
  853. V_snprintf(negativeRecord.m_szDepotPath, sizeof(negativeRecord.m_szDepotPath), "%s/...", path);
  854. // build the clientspec side of the mapping
  855. // aa/b/... //client/BAH/...
  856. //- aa/b/d/... //client/BAH/d/
  857. // find the common strings in both
  858. int iPos = 0;
  859. while (baseRecord.m_szDepotPath[iPos] == negativeRecord.m_szDepotPath[iPos])
  860. ++iPos;
  861. char *pszNegPaths = negativeRecord.m_szDepotPath + iPos;
  862. // append the neg paths to the existing baseRecord.szClientPath
  863. V_strncpy(negativeRecord.m_szClientPath, baseRecord.m_szClientPath, sizeof(negativeRecord.m_szClientPath));
  864. // strip off the last slash
  865. char *pszDir = V_strstr(negativeRecord.m_szClientPath, "/...");
  866. if (pszDir)
  867. {
  868. *pszDir = 0;
  869. }
  870. // append the new client paths to negate
  871. V_strncat(negativeRecord.m_szClientPath, "/", sizeof(negativeRecord.m_szClientPath), COPY_ALL_CHARACTERS);
  872. V_strncat(negativeRecord.m_szClientPath, pszNegPaths, sizeof(negativeRecord.m_szClientPath), COPY_ALL_CHARACTERS);
  873. m_PathMap.InsertAfter(i, negativeRecord);
  874. break;
  875. }
  876. }
  877. }
  878. // calculates the depot root
  879. void GetCommonDepotRoot(char *pszDest, int destSize)
  880. {
  881. int nCount = m_PathMap.Count();
  882. if ( nCount == 0 )
  883. {
  884. pszDest[0] = 0;
  885. return;
  886. }
  887. // walk each mapping, and find the longest common starting substring
  888. V_strncpy(pszDest, m_PathMap[0].m_szDepotPath, destSize);
  889. for (int i = 1; i < nCount; i++)
  890. {
  891. if ( m_PathMap[i].m_bNegative )
  892. continue;
  893. // see how much we compare
  894. for (char *pszD = pszDest, *pszS = m_PathMap[i].m_szDepotPath; *pszD && *pszS; ++pszD, ++pszS)
  895. {
  896. if (*pszD != *pszS)
  897. {
  898. *pszD = 0;
  899. break;
  900. }
  901. }
  902. }
  903. // remove any "..." at the end
  904. char *pszD = V_strstr(pszDest, "...");
  905. if (pszD)
  906. {
  907. *pszD = 0;
  908. }
  909. }
  910. };
  911. //-----------------------------------------------------------------------------
  912. // Purpose: tool for editing clientspecs
  913. //-----------------------------------------------------------------------------
  914. class CClientspecEditUser : public ClientUser
  915. {
  916. public:
  917. CClientspecEditUser( void ) : m_bReadDataVar( false ) {};
  918. void RetrieveClient(const char *pszClient)
  919. {
  920. m_Map.ResetFullClientSpec();
  921. // we just get the entire list
  922. char *argv[] = { "-o", (char *)pszClient, NULL };
  923. s_p4.GetClientApi().SetArgv( 2, argv );
  924. s_p4.GetClientApi().Run("client", this);
  925. }
  926. // saves out the new path spec to the
  927. void WriteClientspec()
  928. {
  929. // write out the new clientspec
  930. char *argv[] = { "-i", NULL, NULL };
  931. s_p4.GetClientApi().SetArgv( 1, argv );
  932. s_p4.GetClientApi().Run("client", this);
  933. }
  934. CClientspecMap m_Map;
  935. bool m_bReadDataVar; //if we encountered a "data" variable, turn off the other parsing
  936. private:
  937. virtual void OutputInfo( char level, const char *data )
  938. {
  939. char szVar[CLIENTSPEC_BUFFER_SIZE], szInfo[CLIENTSPEC_BUFFER_SIZE];
  940. SplitP4Output(data, szVar, szInfo, sizeof(szInfo));
  941. if( szVar[0] != '\0' )
  942. {
  943. if (!V_stricmp(szVar, "data"))
  944. {
  945. m_bReadDataVar = true;
  946. m_Map.ReadClientspec(szInfo);
  947. }
  948. else if( !m_bReadDataVar )
  949. {
  950. if( !V_stricmp( szVar, "root" ) )
  951. {
  952. m_Map.ReadRoot( szInfo );
  953. m_Map.AddVarToFullClientSpec( szVar, szInfo );
  954. }
  955. else if( !V_strnicmp( szVar, "view", 4 ) )
  956. {
  957. if( !V_stricmp( szVar, "view" ) || !V_stricmp( szVar, "view0" ) )
  958. {
  959. //cheat a bit
  960. m_Map.AddStringToFullClientSpec( "View:\n" );
  961. }
  962. m_Map.ReadViewLine( szInfo );
  963. m_Map.AddStringToFullClientSpec( "\t" );
  964. m_Map.AddStringToFullClientSpec( szInfo );
  965. m_Map.AddStringToFullClientSpec( "\n" );
  966. }
  967. else
  968. {
  969. m_Map.AddVarToFullClientSpec( szVar, szInfo );
  970. }
  971. }
  972. }
  973. }
  974. // called to read data
  975. virtual void InputData( StrBuf *strbuf, Error *e )
  976. {
  977. char szView[CLIENTSPEC_BUFFER_SIZE];
  978. m_Map.WriteClientspec(szView, sizeof(szView));
  979. strbuf->Set(szView);
  980. e->Clear();
  981. }
  982. virtual void OutputError( const char *errBuf )
  983. {
  984. Msg("s_p4 error: %s", errBuf);
  985. }
  986. };
  987. //-----------------------------------------------------------------------------
  988. // Purpose: Retreives list of clients
  989. //-----------------------------------------------------------------------------
  990. class CInfoUser : public ClientUser
  991. {
  992. public:
  993. void RetrieveInfo()
  994. {
  995. memset(&m_Client, 0, sizeof(m_Client));
  996. // we just get the entire list
  997. s_p4.GetClientApi().Run("info", this);
  998. }
  999. P4Client_t m_Client;
  1000. private:
  1001. virtual void OutputInfo(char level, const char *data)
  1002. {
  1003. char szCmd[_MAX_PATH];
  1004. char szInfo[_MAX_PATH];
  1005. SplitP4Output(data, szCmd, szInfo, sizeof(szCmd));
  1006. if (!V_stricmp(szCmd, "userName"))
  1007. {
  1008. m_Client.m_sUser = szInfo;
  1009. }
  1010. else if (!V_stricmp(szCmd, "clientName"))
  1011. {
  1012. m_Client.m_sName = szInfo;
  1013. }
  1014. else if (!V_stricmp(szCmd, "clientHost"))
  1015. {
  1016. m_Client.m_sHost = szInfo;
  1017. }
  1018. else if (!V_stricmp(szCmd, "clientRoot"))
  1019. {
  1020. m_Client.m_sLocalRoot = szInfo;
  1021. }
  1022. }
  1023. };
  1024. CInfoUser g_InfoUser;
  1025. // Changelist description structure
  1026. struct ChangelistDesc_t
  1027. {
  1028. int id;
  1029. time_t m_tTimeStamp;
  1030. CUtlSymbol m_sUser;
  1031. CUtlSymbol m_sClient;
  1032. CUtlSymbol m_sStatus;
  1033. CUtlSymbol m_sDescription;
  1034. };
  1035. //-----------------------------------------------------------------------------
  1036. // Purpose: tool for creating new changelists
  1037. //-----------------------------------------------------------------------------
  1038. class CChangelistCreateUser : public CDataRetrievalUser<int>
  1039. {
  1040. public:
  1041. void CreateChangelist( const char *pDescription )
  1042. {
  1043. InitRetrievingData();
  1044. // Will force InputData to be called
  1045. m_pDescription = ( pDescription && pDescription[0] ) ? pDescription : "I'm a loser who didn't type a description. Mock me at your earliest convenience.";
  1046. char *argv[] = { "-i", NULL };
  1047. s_p4.GetClientApi().SetArgv( 1, argv );
  1048. s_p4.GetClientApi().Run("change", this);
  1049. }
  1050. private:
  1051. // called to read data from stdin
  1052. virtual void InputData( StrBuf *strbuf, Error *e )
  1053. {
  1054. char svChangelist[ P4_MAX_INPUT_BUFFER_SIZE + 2048 ];
  1055. P4Client_t &activeClient = s_p4.GetActiveClient();
  1056. V_snprintf( svChangelist, sizeof(svChangelist),
  1057. "Change:\tnew\n\nClient:\t%s\n\nUser:\t%s\n\nStatus:\tnew\n\n"
  1058. "Description:\n\t%s\n\nFiles:\n\n",
  1059. activeClient.m_sName.String(), activeClient.m_sUser.String(), m_pDescription );
  1060. strbuf->Set(svChangelist);
  1061. e->Clear();
  1062. }
  1063. virtual void OutputRecord( int &nChangelist, const char *szCmd, const char *szInfo)
  1064. {
  1065. if (!V_strcmp(szCmd, "Change"))
  1066. {
  1067. nChangelist = atoi(szInfo);
  1068. }
  1069. }
  1070. const char *m_pDescription;
  1071. };
  1072. CChangelistCreateUser g_ChangelistCreateUser;
  1073. //-----------------------------------------------------------------------------
  1074. // Purpose: tool for finding an existing changelist
  1075. //-----------------------------------------------------------------------------
  1076. class CChangelistFindUser : public CDataRetrievalUser<ChangelistDesc_t>
  1077. {
  1078. public:
  1079. void ListChangelists( void )
  1080. {
  1081. InitRetrievingData();
  1082. char *argv[] = { "-l", "-t", "-s", "pending", "-c", s_p4.GetClientApi().GetClient().Text(), NULL };
  1083. s_p4.GetClientApi().SetArgv( 6, argv );
  1084. s_p4.GetClientApi().Run("changes", this);
  1085. }
  1086. private:
  1087. virtual void OutputRecord( ChangelistDesc_t &cl, const char *szCmd, const char *szInfo)
  1088. {
  1089. if ( !V_strcmp( szCmd, "change" ) )
  1090. {
  1091. cl.id = atoi( szInfo );
  1092. }
  1093. else if ( !V_strcmp( szCmd, "time" ) )
  1094. {
  1095. cl.m_tTimeStamp = atoi( szInfo );
  1096. }
  1097. else if ( !V_strcmp( szCmd, "user" ) )
  1098. {
  1099. cl.m_sUser = szInfo;
  1100. }
  1101. else if ( !V_strcmp( szCmd, "client" ) )
  1102. {
  1103. cl.m_sClient = szInfo;
  1104. }
  1105. else if ( !V_strcmp( szCmd, "status" ) )
  1106. {
  1107. cl.m_sStatus = szInfo;
  1108. }
  1109. else if ( !V_strcmp( szCmd, "desc" ) )
  1110. {
  1111. CUtlVector<char> arrInfo;
  1112. arrInfo.SetCount( 1 + strlen( szInfo ) );
  1113. memcpy( arrInfo.Base(), szInfo, arrInfo.Count() );
  1114. for ( int nCount = arrInfo.Count() - 1; nCount -- > 0; )
  1115. {
  1116. if ( isspace( arrInfo[ nCount ] ) )
  1117. arrInfo[ nCount ] = 0;
  1118. else
  1119. break;
  1120. }
  1121. cl.m_sDescription = arrInfo.Base();
  1122. }
  1123. }
  1124. };
  1125. CChangelistFindUser g_ChangelistFindUser;
  1126. //-----------------------------------------------------------------------------
  1127. // Purpose: only used to handle the error callback
  1128. //-----------------------------------------------------------------------------
  1129. class CErrorHandlerUser : public ClientUser
  1130. {
  1131. public:
  1132. CErrorHandlerUser() : m_errorSeverity( E_EMPTY ), m_uiFlags( 0 ) {}
  1133. virtual void OutputError( const char *errBuf )
  1134. {
  1135. m_errorSeverity = max( m_errorSeverity, E_WARN ); // this is a guess - it could have been E_FATAL or E_FAILED
  1136. Msg("s_p4 error: %s", errBuf);
  1137. m_errorBuf.Append( errBuf );
  1138. }
  1139. virtual void HandleError( Error *err )
  1140. {
  1141. m_errorSeverity = max( m_errorSeverity, ( ErrorSeverity )err->GetSeverity() );
  1142. StrBuf buf;
  1143. err->Fmt( buf, EF_NEWLINE );
  1144. if ( V_strstr( buf.Text(), "can't edit exclusive file already opened" ) )
  1145. {
  1146. m_errorSeverity = max( m_errorSeverity, E_WARN ); // s_p4 sends this is an info message, even though the file doesn't get edited!
  1147. }
  1148. if ( ( m_uiFlags & eDisableFiltering ) || ShallOutputErrorStringBuffer( buf ) )
  1149. Msg( "%s: %s", err->FmtSeverity(), buf.Text() );
  1150. m_errorBuf.Append( &buf );
  1151. }
  1152. // Message is the only method that gets called on a s_p4 error
  1153. // even though the s_p4 documentation claims that HandleError (and therefore OutputError)
  1154. // should get called via the defalt Message implementation
  1155. virtual void Message( Error *err )
  1156. {
  1157. HandleError( err );
  1158. }
  1159. void ResetErrorState()
  1160. {
  1161. m_errorSeverity = E_EMPTY;
  1162. m_errorBuf.Clear();
  1163. }
  1164. ErrorSeverity GetErrorState()
  1165. {
  1166. return m_errorSeverity;
  1167. }
  1168. const char *GetErrorString()
  1169. {
  1170. return m_errorBuf.Text();
  1171. }
  1172. void SetErrorString( const char *errStr, ErrorSeverity severity )
  1173. {
  1174. m_errorBuf.Set( errStr );
  1175. m_errorSeverity = severity;
  1176. }
  1177. enum Flags {
  1178. eDisableFiltering = 1 << 0, // Disable filtering output
  1179. eFilterClUselessSpew = 1 << 1, // Filter "already opened", "add of exisiting", "can't change" msgs
  1180. };
  1181. // Sets the new flag mask, add prevails over remove if the flag specified in both.
  1182. // SetFlags( x, 0 ) - enables flag x
  1183. // SetFlags( 0, x ) - removes flag x
  1184. // SetFlags( mask, ~0 ) - completely sets the flag mask
  1185. uint32 SetFlags( uint32 uiAdd, uint32 uiRemove ) { uint32 uiOld = m_uiFlags; m_uiFlags = ( ( m_uiFlags & ~uiRemove ) | uiAdd ); return uiOld; }
  1186. protected:
  1187. // Performs filtering before the message is output to screen,
  1188. // if "ShallOutputErrorStringBuffer" returns false, then the message
  1189. // is not printed on screen, otherwise the modified strbug is printed.
  1190. virtual bool ShallOutputErrorStringBuffer( StrBuf &buf );
  1191. private:
  1192. ErrorSeverity m_errorSeverity;
  1193. StrBuf m_errorBuf;
  1194. uint32 m_uiFlags;
  1195. };
  1196. CErrorHandlerUser g_ErrorHandlerUser;
  1197. bool CErrorHandlerUser::ShallOutputErrorStringBuffer( StrBuf &buf )
  1198. {
  1199. char const *pText = buf.Text();
  1200. if ( m_uiFlags & eFilterClUselessSpew )
  1201. {
  1202. if ( V_strstr( pText, "already opened for edit" ) )
  1203. return false;
  1204. if ( V_strstr( pText, "already opened for add" ) )
  1205. return false;
  1206. if ( V_strstr( pText, "currently opened for edit" ) )
  1207. return false;
  1208. if ( V_strstr( pText, "currently opened for add" ) )
  1209. return false;
  1210. if ( V_strstr( pText, "add of existing file" ) )
  1211. return false;
  1212. if ( V_strstr( pText, "add existing file" ) )
  1213. return false;
  1214. if ( V_strstr( pText, "can't change from" ) )
  1215. return false;
  1216. if ( V_strstr( pText, "no such file" ) )
  1217. return false;
  1218. if ( V_strstr( pText, "not on client" ) )
  1219. return false;
  1220. }
  1221. return true;
  1222. }
  1223. //-----------------------------------------------------------------------------
  1224. // Purpose: tool for creating new changelists
  1225. //-----------------------------------------------------------------------------
  1226. class CSubmitUser : public ClientUser
  1227. {
  1228. public:
  1229. void Submit( int nChangeList )
  1230. {
  1231. // NOTE: -i doesn't appear to work with -c
  1232. // We have to set up the description when creating the changelist in the first place
  1233. m_pDescription = NULL;
  1234. char pBuf[128];
  1235. V_snprintf( pBuf, sizeof(pBuf), "%d", nChangeList );
  1236. char *argv[] = { "-c", pBuf, NULL };
  1237. s_p4.GetClientApi().SetArgv( 2, argv );
  1238. s_p4.GetClientApi().Run( "submit", &g_ErrorHandlerUser );
  1239. }
  1240. private:
  1241. // called to read data from stdin
  1242. virtual void InputData( StrBuf *strbuf, Error *e )
  1243. {
  1244. Assert( m_pDescription );
  1245. char svChangelist[1024];
  1246. P4Client_t &activeClient = s_p4.GetActiveClient();
  1247. V_snprintf( svChangelist, sizeof(svChangelist),
  1248. "Change:\tnew\n\nClient:\t%s\n\nUser:\t%s\n\nStatus:\tnew\n\n"
  1249. "Description:\n\t%s\n\nFiles:\n\n",
  1250. activeClient.m_sName.String(), activeClient.m_sUser.String(), m_pDescription );
  1251. strbuf->Set(svChangelist);
  1252. e->Clear();
  1253. }
  1254. const char *m_pDescription;
  1255. };
  1256. CSubmitUser g_SubmitUser;
  1257. int FindOrCreateChangelist( const char *pDescription )
  1258. {
  1259. int iCreatedEmptyCl = 0, iFoundChangelist = 0;
  1260. CUtlMap<int, ChangelistDesc_t const *> mapFoundChangelists( DefLessFunc( int ) );
  1261. CUtlSymbol symDesc = pDescription;
  1262. find_changelists:
  1263. mapFoundChangelists.RemoveAll();
  1264. g_ChangelistFindUser.ListChangelists();
  1265. for ( int k = 0; k < g_ChangelistFindUser.GetData().Count(); ++ k )
  1266. {
  1267. ChangelistDesc_t const &cl = g_ChangelistFindUser.GetData()[ k ];
  1268. if ( symDesc == cl.m_sDescription )
  1269. mapFoundChangelists.Insert( cl.id, &cl );
  1270. }
  1271. if ( mapFoundChangelists.Count() )
  1272. iFoundChangelist = mapFoundChangelists.Key( mapFoundChangelists.FirstInorder() );
  1273. else if ( iCreatedEmptyCl > 0 )
  1274. return iCreatedEmptyCl;
  1275. if ( iFoundChangelist > 0 )
  1276. {
  1277. // Check if we created a changelist that has to be deleted
  1278. if ( iCreatedEmptyCl > 0 && iCreatedEmptyCl != iFoundChangelist )
  1279. {
  1280. // Delete changelist
  1281. char chClNumber[50];
  1282. sprintf( chClNumber, "%d", iCreatedEmptyCl );
  1283. char *argv[] = { "-d", chClNumber, NULL };
  1284. s_p4.GetClientApi().SetArgv( 2, argv );
  1285. s_p4.GetClientApi().Run( "change", &g_ErrorHandlerUser );
  1286. }
  1287. return iFoundChangelist;
  1288. }
  1289. else
  1290. {
  1291. // We did not find a changelist, create a new one
  1292. g_ChangelistCreateUser.CreateChangelist( pDescription );
  1293. iCreatedEmptyCl = g_ChangelistCreateUser.GetData().Count() ? g_ChangelistCreateUser.GetData()[0] : 0;
  1294. if ( !iCreatedEmptyCl )
  1295. return 0;
  1296. // Now we have a changelist created, check again to avoid multithreading bugs
  1297. goto find_changelists;
  1298. }
  1299. }
  1300. //-----------------------------------------------------------------------------
  1301. // Destructor
  1302. //-----------------------------------------------------------------------------
  1303. CP4::~CP4()
  1304. {
  1305. // Prevents a hang if Shutdown isn't called before the process exits
  1306. if ( m_bConnectedToServer )
  1307. {
  1308. Shutdown();
  1309. }
  1310. }
  1311. //-----------------------------------------------------------------------------
  1312. // Connect, disconnect
  1313. //-----------------------------------------------------------------------------
  1314. bool CP4::Connect( CreateInterfaceFn factory )
  1315. {
  1316. #if !defined(STANDALONE_VPC)
  1317. g_pFileSystem = (IFileSystem*)factory( FILESYSTEM_INTERFACE_VERSION, NULL );
  1318. return ( g_pFileSystem != NULL );
  1319. #else
  1320. return true;
  1321. #endif
  1322. }
  1323. void CP4::Disconnect()
  1324. {
  1325. #if !defined(STANDALONE_VPC)
  1326. g_pFileSystem = NULL;
  1327. #endif
  1328. }
  1329. //-----------------------------------------------------------------------------
  1330. // Purpose: Startup
  1331. //-----------------------------------------------------------------------------
  1332. InitReturnVal_t CP4::Init()
  1333. {
  1334. // set the protocol return all data as key/value pairs
  1335. m_Client.SetProtocol( "tag", "" );
  1336. // connect to the s_p4 server
  1337. Error e;
  1338. m_Client.Init( &e );
  1339. m_bConnectedToServer = e.Test() == 0 && m_Client.Dropped() == 0;
  1340. RefreshClientData();
  1341. return INIT_OK;
  1342. }
  1343. //-----------------------------------------------------------------------------
  1344. // Purpose: Cleanup
  1345. //-----------------------------------------------------------------------------
  1346. void CP4::Shutdown()
  1347. {
  1348. Error e;
  1349. m_Client.Final(&e);
  1350. m_bConnectedToServer = false;
  1351. }
  1352. //-----------------------------------------------------------------------------
  1353. // Purpose: refreshes client data
  1354. //-----------------------------------------------------------------------------
  1355. void CP4::RefreshClientData()
  1356. {
  1357. if ( !m_bConnectedToServer )
  1358. return;
  1359. // retrieve our login info
  1360. g_InfoUser.RetrieveInfo();
  1361. m_ActiveClient = g_InfoUser.m_Client;
  1362. // calculate our common depot root
  1363. CClientspecEditUser user;
  1364. user.RetrieveClient( GetActiveClient().m_sName.String() );
  1365. user.m_Map.GetCommonDepotRoot(m_szDepotRoot, sizeof(m_szDepotRoot));
  1366. m_iDepotRootLength = V_strlen(m_szDepotRoot);
  1367. m_ClientMapping = user.m_Map.m_PathMap;
  1368. V_strncpy( m_szLocalRoot, user.m_Map.m_szLocalPath, sizeof(m_szLocalRoot) );
  1369. m_iLocalRootLength = V_strlen(m_szLocalRoot);
  1370. SetOpenFileChangeList( m_sChangeListName.String() );
  1371. }
  1372. //-----------------------------------------------------------------------------
  1373. // Convert a depot file to a local file user the current client mapping
  1374. //-----------------------------------------------------------------------------
  1375. bool CP4::DepotFileToLocalFile( const char *pDepotFile, char *pLocalPath, int nBufLen )
  1376. {
  1377. // Need to construct valid local paths since opened doesn't do it for us
  1378. char pClientPattern[MAX_PATH];
  1379. V_snprintf( pClientPattern, sizeof(pClientPattern), "//%s/", m_Client.GetClient().Text() );
  1380. int nClientLen = V_strlen( pClientPattern );
  1381. int nMatchCount = 0;
  1382. int nCount = m_ClientMapping.Count();
  1383. for ( int i = 0; i < nCount; ++i )
  1384. {
  1385. const char *pDepotPath = m_ClientMapping[i].m_szDepotPath;
  1386. int nLen = V_strlen( pDepotPath );
  1387. if ( nMatchCount > nLen )
  1388. continue;
  1389. bool bRecursive = !V_stricmp( &pDepotPath[nLen-4], "/..." );
  1390. bool bInDirectory = !V_stricmp( &pDepotPath[nLen-2], "/*" );
  1391. if ( !bRecursive && !bInDirectory )
  1392. continue;
  1393. // FIXME: Do this fixup when we create m_ClientMapping?
  1394. char pMatchingString[MAX_PATH];
  1395. V_strncpy( pMatchingString, pDepotPath, bRecursive ? nLen-2 : nLen );
  1396. if ( V_stristr( pDepotFile, pMatchingString ) != pDepotFile )
  1397. continue;
  1398. // Skip subdirectories if it's in the directory
  1399. if ( bInDirectory )
  1400. {
  1401. const char *pRelativePath = pDepotFile + nLen - 1;
  1402. if ( strchr( pRelativePath, '\\' ) || strchr( pRelativePath, '/' ) )
  1403. continue;
  1404. }
  1405. const char *pClientPath = s_p4.String( m_ClientMapping[i].m_szClientPath );
  1406. int nPathLen = V_strlen( pClientPath );
  1407. bool bClientRecursive = !V_stricmp( &pClientPath[nPathLen-4], "/..." );
  1408. bool bClientInDirectory = !V_stricmp( &pClientPath[nPathLen-2], "/*" );
  1409. if ( !bClientRecursive && !bClientInDirectory )
  1410. continue;
  1411. char pTruncatedClientPath[MAX_PATH];
  1412. V_strncpy( pTruncatedClientPath, pClientPath, bClientRecursive ? nPathLen-2 : nPathLen );
  1413. if ( V_stristr( pTruncatedClientPath, pClientPattern ) != pTruncatedClientPath )
  1414. continue;
  1415. // This is necessary if someone has a trailing slash on their root as in c:\valve\main\
  1416. // Otherwise it'll return something like c:\valve\main\\src\blah.cpp and confuse VPC.
  1417. char szLocalRootWithoutSlashes[MAX_PATH];
  1418. V_strncpy( szLocalRootWithoutSlashes, s_p4.GetLocalRoot(), sizeof( szLocalRootWithoutSlashes ) );
  1419. V_StripTrailingSlash( szLocalRootWithoutSlashes );
  1420. if ( nClientLen < nPathLen - 3 )
  1421. {
  1422. V_snprintf( pLocalPath, nBufLen, "%s\\%s%s", szLocalRootWithoutSlashes, pTruncatedClientPath + nClientLen, bRecursive ? pDepotFile + nLen - 3 : pDepotFile + nLen - 1 );
  1423. }
  1424. else
  1425. {
  1426. V_snprintf( pLocalPath, nBufLen, "%s\\%s", szLocalRootWithoutSlashes, bRecursive ? pDepotFile + nLen - 3 : pDepotFile + nLen - 1 );
  1427. }
  1428. V_FixSlashes( pLocalPath );
  1429. nMatchCount = nLen;
  1430. }
  1431. return ( nMatchCount > 0 );
  1432. }
  1433. //-----------------------------------------------------------------------------
  1434. // Refreshes the current client from s_p4 settings
  1435. //-----------------------------------------------------------------------------
  1436. void CP4::RefreshActiveClient()
  1437. {
  1438. if ( !IsConnectedToServer() )
  1439. return;
  1440. RefreshClientData();
  1441. }
  1442. //-----------------------------------------------------------------------------
  1443. // Query interface
  1444. //-----------------------------------------------------------------------------
  1445. void *CP4::QueryInterface( const char *pInterfaceName )
  1446. {
  1447. return Sys_GetFactoryThis()( pInterfaceName, NULL );
  1448. }
  1449. //-----------------------------------------------------------------------------
  1450. // Gets a string for a symbol
  1451. //-----------------------------------------------------------------------------
  1452. const char *CP4::String( CUtlSymbol s ) const
  1453. {
  1454. return s.String();
  1455. }
  1456. //-----------------------------------------------------------------------------
  1457. // Purpose: data accessor
  1458. //-----------------------------------------------------------------------------
  1459. const char *CP4::GetDepotRoot()
  1460. {
  1461. if ( !IsConnectedToServer() )
  1462. return NULL;
  1463. return m_szDepotRoot;
  1464. }
  1465. //-----------------------------------------------------------------------------
  1466. // Purpose: data accessor
  1467. //-----------------------------------------------------------------------------
  1468. int CP4::GetDepotRootLength()
  1469. {
  1470. if ( !IsConnectedToServer() )
  1471. return -1;
  1472. return m_iDepotRootLength;
  1473. }
  1474. const char *CP4::GetLocalRoot()
  1475. {
  1476. if ( !IsConnectedToServer() )
  1477. return NULL;
  1478. return m_szLocalRoot;
  1479. }
  1480. int CP4::GetLocalRootLength()
  1481. {
  1482. if ( !IsConnectedToServer() )
  1483. return -1;
  1484. return m_iLocalRootLength;
  1485. }
  1486. //-----------------------------------------------------------------------------
  1487. // Purpose: translates filespecs to depotFile paths
  1488. //-----------------------------------------------------------------------------
  1489. void CP4::GetDepotFilePath(char *depotFilePath, const char *filespec, int size)
  1490. {
  1491. if ( !IsConnectedToServer() )
  1492. {
  1493. if ( size > 0 )
  1494. {
  1495. depotFilePath[ 0 ] = '\0';
  1496. }
  1497. return;
  1498. }
  1499. g_WhereUser.RetrieveWhereabouts(filespec);
  1500. V_strncpy(depotFilePath, g_WhereUser.GetData()[0].m_sDepotFile.String(), size);
  1501. }
  1502. //-----------------------------------------------------------------------------
  1503. // Purpose: translates filespecs to clientFile paths
  1504. //-----------------------------------------------------------------------------
  1505. void CP4::GetClientFilePath(char *clientFilePath, const char *filespec, int size)
  1506. {
  1507. if ( !IsConnectedToServer() )
  1508. {
  1509. if ( size > 0 )
  1510. {
  1511. clientFilePath[ 0 ] = '\0';
  1512. }
  1513. return;
  1514. }
  1515. g_WhereUser.RetrieveWhereabouts(filespec);
  1516. V_strncpy(clientFilePath, g_WhereUser.GetData()[0].m_sClientFile.String(), size);
  1517. }
  1518. //-----------------------------------------------------------------------------
  1519. // Purpose: translates filespecs to localFile paths
  1520. //-----------------------------------------------------------------------------
  1521. void CP4::GetLocalFilePath(char *localFilePath, const char *filespec, int size)
  1522. {
  1523. if ( !IsConnectedToServer() )
  1524. {
  1525. if ( size > 0 )
  1526. {
  1527. localFilePath[ 0 ] = '\0';
  1528. }
  1529. return;
  1530. }
  1531. g_WhereUser.RetrieveWhereabouts(filespec);
  1532. V_strncpy(localFilePath, g_WhereUser.GetData()[0].m_sLocalFile.String(), size);
  1533. }
  1534. //-----------------------------------------------------------------------------
  1535. // Purpose: returns a list of files
  1536. //-----------------------------------------------------------------------------
  1537. CUtlVector<P4File_t> &CP4::GetFileList( const char *pPath )
  1538. {
  1539. if ( !IsConnectedToServer() )
  1540. {
  1541. static CUtlVector< P4File_t > dummy;
  1542. return dummy;
  1543. }
  1544. CScopedDirClientSpec spec( pPath );
  1545. g_FileUser.RetrieveDir( pPath );
  1546. return g_FileUser.GetData();
  1547. }
  1548. //-----------------------------------------------------------------------------
  1549. // retreives the list of files in a path, using a known client spec
  1550. //-----------------------------------------------------------------------------
  1551. CUtlVector<P4File_t> &CP4::GetFileListUsingClientSpec( const char *pPath, const char *pClientSpec )
  1552. {
  1553. if ( !IsConnectedToServer() )
  1554. {
  1555. static CUtlVector< P4File_t > dummy;
  1556. return dummy;
  1557. }
  1558. CScopedClientSpec spec( pClientSpec );
  1559. g_FileUser.RetrieveDir( pPath );
  1560. return g_FileUser.GetData();
  1561. }
  1562. //-----------------------------------------------------------------------------
  1563. // Purpose: returns the list of files opened for edit/integrate/delete
  1564. //-----------------------------------------------------------------------------
  1565. void CP4::GetOpenedFileList( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly )
  1566. {
  1567. if ( !IsConnectedToServer() )
  1568. {
  1569. fileList.RemoveAll();
  1570. return;
  1571. }
  1572. g_FileUser.RetrieveOpenedFiles( fileList, bDefaultChangeOnly );
  1573. }
  1574. void CP4::GetFileListInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList )
  1575. {
  1576. if ( !IsConnectedToServer() )
  1577. {
  1578. fileList.RemoveAll();
  1579. return;
  1580. }
  1581. g_FileUser.RetrieveFilesInChangelist( changeListNumber, fileList );
  1582. }
  1583. void CP4::GetOpenedFileList( const char *pRootDirectory, CUtlVector<P4File_t> &fileList )
  1584. {
  1585. if ( !IsConnectedToServer() )
  1586. {
  1587. fileList.RemoveAll();
  1588. return;
  1589. }
  1590. CScopedDirClientSpec spec( pRootDirectory );
  1591. g_FileUser.RetrieveOpenedFiles( fileList, false );
  1592. }
  1593. void CP4::GetOpenedFileListInPath( const char *pPathID, CUtlVector<P4File_t> &fileList )
  1594. {
  1595. if ( !IsConnectedToServer() )
  1596. {
  1597. fileList.RemoveAll();
  1598. return;
  1599. }
  1600. CScopedPathClientSpec spec( pPathID );
  1601. g_FileUser.RetrieveOpenedFiles( fileList, false );
  1602. }
  1603. //-----------------------------------------------------------------------------
  1604. // Purpose: file history
  1605. //-----------------------------------------------------------------------------
  1606. CUtlVector<P4Revision_t> &CP4::GetRevisionList(const char *path, bool bIsDir)
  1607. {
  1608. if ( !IsConnectedToServer() )
  1609. {
  1610. static CUtlVector< P4Revision_t > dummy;
  1611. return dummy;
  1612. }
  1613. g_RevisionHistoryUser.RetrieveHistory(path, bIsDir);
  1614. return g_RevisionHistoryUser.GetData();
  1615. }
  1616. //-----------------------------------------------------------------------------
  1617. // Purpose: returns a list of clients
  1618. //-----------------------------------------------------------------------------
  1619. CUtlVector<P4Client_t> &CP4::GetClientList()
  1620. {
  1621. if ( !IsConnectedToServer() )
  1622. {
  1623. static CUtlVector< P4Client_t > dummy;
  1624. return dummy;
  1625. }
  1626. g_ClientspecUser.RetrieveClients();
  1627. return g_ClientspecUser.GetData();
  1628. }
  1629. //-----------------------------------------------------------------------------
  1630. // Purpose: sets the active client
  1631. //-----------------------------------------------------------------------------
  1632. void CP4::SetActiveClient(const char *clientname)
  1633. {
  1634. if ( !IsConnectedToServer() )
  1635. return;
  1636. m_Client.SetClient(clientname);
  1637. RefreshClientData();
  1638. }
  1639. //-----------------------------------------------------------------------------
  1640. // Purpose: returns the name of the currently opened clientspec
  1641. //-----------------------------------------------------------------------------
  1642. P4Client_t &CP4::GetActiveClient()
  1643. {
  1644. return m_ActiveClient;
  1645. }
  1646. //-----------------------------------------------------------------------------
  1647. // Purpose: cloaks a folder from the current view
  1648. //-----------------------------------------------------------------------------
  1649. void CP4::RemovePathFromActiveClientspec(const char *path)
  1650. {
  1651. if ( !IsConnectedToServer() )
  1652. return;
  1653. // read in the clientspec
  1654. CClientspecEditUser user;
  1655. user.RetrieveClient(GetActiveClient().m_sName.String());
  1656. // get the extra info
  1657. user.m_Map.RemovePathFromClient(path);
  1658. // refresh the list
  1659. user.WriteClientspec();
  1660. }
  1661. //-----------------------------------------------------------------------------
  1662. // Finds a client spec for a directory. Is destructive to the passed-in directory
  1663. //-----------------------------------------------------------------------------
  1664. bool CP4::GetClientSpecForDirectory( const char *pFullPathDir, char *pClientSpec, int nMaxLen )
  1665. {
  1666. if ( nMaxLen == 0 )
  1667. return false;
  1668. pClientSpec[ 0 ] = '\0';
  1669. if ( !IsConnectedToServer() )
  1670. return false;
  1671. char pCurPath[ MAX_PATH ];
  1672. V_strncpy( pCurPath, pFullPathDir, sizeof(pCurPath) );
  1673. V_StripTrailingSlash( pCurPath );
  1674. characterset_t breaks;
  1675. CharacterSetBuild( &breaks, "=\n");
  1676. do
  1677. {
  1678. char pP4ConfigPath[MAX_PATH];
  1679. V_strncpy( pP4ConfigPath, pCurPath, MAX_PATH );
  1680. V_strncat( pP4ConfigPath, "\\p4config", MAX_PATH, MAX_PATH );
  1681. if ( FileExists( pP4ConfigPath ) )
  1682. {
  1683. char temp[1024];
  1684. CUtlBuffer buf( temp, sizeof(temp), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
  1685. if ( ReadFile( pP4ConfigPath, NULL, buf ) )
  1686. {
  1687. while ( buf.IsValid() )
  1688. {
  1689. char token[256], value[256];
  1690. if ( !buf.ParseToken( &breaks, token, sizeof(token) ) )
  1691. break;
  1692. if ( !buf.GetToken("=") )
  1693. break;
  1694. if ( !buf.ParseToken( &breaks, value, sizeof(value) ) )
  1695. break;
  1696. if ( !V_stricmp(token, "p4client") )
  1697. {
  1698. V_strncpy( pClientSpec, value, nMaxLen );
  1699. return true;
  1700. }
  1701. }
  1702. Warning( "Unable to read file %s!\n", pP4ConfigPath );
  1703. }
  1704. }
  1705. V_StripLastDir( pCurPath, sizeof(pCurPath) );
  1706. V_StripTrailingSlash( pCurPath );
  1707. if ( V_strlen( pCurPath ) <= 2 )
  1708. break;
  1709. } while (true);
  1710. return false;
  1711. }
  1712. //-----------------------------------------------------------------------------
  1713. // Returns which clientspec a file lies under
  1714. //-----------------------------------------------------------------------------
  1715. bool CP4::GetClientSpecForFile( const char *pFullPath, char *pClientSpec, int nMaxLen )
  1716. {
  1717. // Strip off the file name
  1718. char pCurPath[MAX_PATH];
  1719. V_strncpy( pCurPath, pFullPath, sizeof(pCurPath) );
  1720. V_StripFilename( pCurPath );
  1721. // Recursively search subdirectories until we find a p4config file.
  1722. return GetClientSpecForDirectory( pCurPath, pClientSpec, nMaxLen );
  1723. }
  1724. //-----------------------------------------------------------------------------
  1725. // Returns which clientspec a filesystem path ID lies under
  1726. //-----------------------------------------------------------------------------
  1727. bool CP4::GetClientSpecForPath( const char *pPathId, char *pClientSpec, int nMaxLen )
  1728. {
  1729. if ( !IsConnectedToServer() )
  1730. {
  1731. if ( nMaxLen > 0 )
  1732. {
  1733. pClientSpec[ 0 ] = '\0';
  1734. }
  1735. return false;
  1736. }
  1737. char pPathBuf[2048];
  1738. #if defined( STANDALONE_VPC )
  1739. V_strncpy( pPathBuf, ".", V_ARRAYSIZE( pPathBuf ) );
  1740. #else
  1741. if ( g_pFileSystem->GetSearchPath( pPathId, false, pPathBuf, sizeof(pPathBuf) ) == 0 )
  1742. return false;
  1743. #endif
  1744. // FIXME: Would be faster to not check for duplication and just
  1745. // pick the first client spec it sees. Should I not test the additional paths?
  1746. bool bFirstPath = true;
  1747. bool bFoundClientSpec = false;
  1748. char pTempClientSpec[MAX_PATH];
  1749. char *pPath = pPathBuf;
  1750. while ( pPath )
  1751. {
  1752. // Find the next path
  1753. char *pCurPath = pPath;
  1754. char *pSemi = strchr( pPath, ';' );
  1755. if ( pSemi )
  1756. {
  1757. *pSemi = 0;
  1758. pPath = pSemi+1;
  1759. }
  1760. else
  1761. {
  1762. pPath = NULL;
  1763. }
  1764. // Recursively search subdirectories until we find a p4config file.
  1765. if ( GetClientSpecForDirectory( pCurPath, pClientSpec, nMaxLen ) )
  1766. {
  1767. bFoundClientSpec = true;
  1768. if ( bFirstPath )
  1769. {
  1770. V_strncpy( pTempClientSpec, pClientSpec, sizeof(pTempClientSpec) );
  1771. }
  1772. else
  1773. {
  1774. // Paths are on different client specs
  1775. if ( V_stricmp( pClientSpec, pTempClientSpec ) )
  1776. {
  1777. pClientSpec[0] = 0;
  1778. return false;
  1779. }
  1780. }
  1781. }
  1782. }
  1783. return bFoundClientSpec;
  1784. }
  1785. void CP4::SetOpenFileChangeList( const char *pChangeListName )
  1786. {
  1787. if ( !m_sChangeListName.Length() ||
  1788. !pChangeListName || !*pChangeListName ||
  1789. strcmp( m_sChangeListName.String(), pChangeListName ) ||
  1790. pChangeListName == m_sChangeListName.String() )
  1791. {
  1792. m_sChangeListName.Set( pChangeListName );
  1793. m_sCachedChangeListNum.Set( "0" );
  1794. m_nCachedChangeListNumber = 0;
  1795. // Valid changelist
  1796. {
  1797. uint32 uiSpewFlag = CErrorHandlerUser::eFilterClUselessSpew;
  1798. int bAdd = m_sChangeListName.Length();
  1799. g_ErrorHandlerUser.SetFlags( bAdd ? uiSpewFlag : 0, bAdd ? 0 : uiSpewFlag );
  1800. }
  1801. }
  1802. }
  1803. char const * CP4::GetOpenFileChangeListNum()
  1804. {
  1805. if ( m_sChangeListName.Length() )
  1806. {
  1807. if ( !m_nCachedChangeListNumber )
  1808. {
  1809. m_nCachedChangeListNumber = FindOrCreateChangelist( m_sChangeListName.String() );
  1810. if ( m_nCachedChangeListNumber )
  1811. m_sCachedChangeListNum.Format( "%d", m_nCachedChangeListNumber );
  1812. }
  1813. if ( m_nCachedChangeListNumber )
  1814. return m_sCachedChangeListNum.String();
  1815. }
  1816. return NULL;
  1817. }
  1818. static bool MakeFilesWritable( int nCount, const char **ppFullPathList )
  1819. {
  1820. bool bResult = true;
  1821. // Make sure we can make as many files writable as possible
  1822. for ( int k = 0; k < nCount; ++ k )
  1823. {
  1824. char const *szFile = ppFullPathList[ k ];
  1825. if ( !access( szFile, 02 ) )
  1826. continue;
  1827. if ( !chmod( szFile, _S_IWRITE | _S_IREAD ) )
  1828. continue;
  1829. bResult = false;
  1830. }
  1831. return bResult;
  1832. }
  1833. //-----------------------------------------------------------------------------
  1834. // Purpose:
  1835. //-----------------------------------------------------------------------------
  1836. bool CP4::OpenFileForAdd( const char *fullpath )
  1837. {
  1838. return PerformPerforceOpCurChangeList( "add", fullpath );
  1839. }
  1840. //-----------------------------------------------------------------------------
  1841. // Purpose:
  1842. //-----------------------------------------------------------------------------
  1843. bool CP4::OpenFileForEdit( const char *fullpath )
  1844. {
  1845. if ( !PerformPerforceOpCurChangeList( "edit", fullpath ) )
  1846. return false;
  1847. return MakeFilesWritable( 1, &fullpath );
  1848. }
  1849. //-----------------------------------------------------------------------------
  1850. // Purpose:
  1851. //-----------------------------------------------------------------------------
  1852. bool CP4::OpenFileForDelete( const char *pFullPath )
  1853. {
  1854. return PerformPerforceOpCurChangeList( "delete", pFullPath );
  1855. }
  1856. //-----------------------------------------------------------------------------
  1857. // Purpose:
  1858. //-----------------------------------------------------------------------------
  1859. bool CP4::SyncFile( const char *pFullPath, int nRevision )
  1860. {
  1861. char szFileOptions[ MAX_PATH + 128 ];
  1862. if ( nRevision >= 0 )
  1863. { // sync to a specific revision
  1864. V_snprintf( szFileOptions, sizeof( szFileOptions ), "%s#%d", pFullPath, nRevision );
  1865. }
  1866. else
  1867. { // sync to the head revision
  1868. V_snprintf( szFileOptions, sizeof( szFileOptions ), "%s#head", pFullPath );
  1869. }
  1870. return PerformPerforceOp( "sync", szFileOptions );
  1871. }
  1872. //-----------------------------------------------------------------------------
  1873. // Submit file
  1874. //-----------------------------------------------------------------------------
  1875. bool CP4::SubmitFile( const char *pFullPath, const char *pDescription )
  1876. {
  1877. return SubmitFiles( 1, &pFullPath, pDescription );
  1878. }
  1879. //-----------------------------------------------------------------------------
  1880. // Revert file
  1881. //-----------------------------------------------------------------------------
  1882. bool CP4::RevertFile( const char *pFullPath )
  1883. {
  1884. return PerformPerforceOp( "revert", pFullPath );
  1885. }
  1886. //-----------------------------------------------------------------------------
  1887. // Helper for operations on multiple files
  1888. //-----------------------------------------------------------------------------
  1889. bool CP4::PerformPerforceOp( PerforceOp_t op, int nCount, const char **ppFullPathList, const char *pDescription )
  1890. {
  1891. g_ErrorHandlerUser.ResetErrorState();
  1892. if ( !IsConnectedToServer() )
  1893. {
  1894. g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL );
  1895. return false;
  1896. }
  1897. if ( nCount == 0 )
  1898. return true;
  1899. char pOldClientSpec[MAX_PATH];
  1900. V_strncpy( pOldClientSpec, m_Client.GetClient().Text(), sizeof(pOldClientSpec) );
  1901. char pCurrentClientSpec[MAX_PATH];
  1902. V_strncpy( pCurrentClientSpec, pOldClientSpec, sizeof(pCurrentClientSpec) );
  1903. int nFirstIndex = 0;
  1904. while ( nFirstIndex < nCount )
  1905. {
  1906. char pClientSpec[MAX_PATH];
  1907. bool bChangeSpec = false;
  1908. int i;
  1909. for ( i = nFirstIndex; i < nCount; ++i )
  1910. {
  1911. if ( s_p4.GetClientSpecForFile( ppFullPathList[i], pClientSpec, sizeof(pClientSpec) ) )
  1912. {
  1913. if ( V_stricmp( pCurrentClientSpec, pClientSpec ) )
  1914. {
  1915. bChangeSpec = true;
  1916. break;
  1917. }
  1918. }
  1919. }
  1920. if ( i != nFirstIndex )
  1921. {
  1922. op( i - nFirstIndex, &ppFullPathList[nFirstIndex], pDescription );
  1923. nFirstIndex = i;
  1924. }
  1925. if ( bChangeSpec )
  1926. {
  1927. s_p4.SetActiveClient( pClientSpec );
  1928. V_strncpy( pCurrentClientSpec, pClientSpec, sizeof(pCurrentClientSpec) );
  1929. }
  1930. }
  1931. if ( V_stricmp( pCurrentClientSpec, pOldClientSpec ) )
  1932. {
  1933. s_p4.SetActiveClient( pOldClientSpec );
  1934. }
  1935. return g_ErrorHandlerUser.GetErrorState() < E_WARN;
  1936. }
  1937. static const char *s_pOperation;
  1938. void SimplePerforceOp( int nCount, const char **ppFullPathList, const char *pDescription )
  1939. {
  1940. s_p4.GetClientApi().SetArgv( nCount, const_cast<char**>( ppFullPathList ) );
  1941. s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser );
  1942. }
  1943. bool CP4::PerformPerforceOp( const char *pOperation, int nCount, const char **ppFullPathList )
  1944. {
  1945. s_pOperation = pOperation;
  1946. return PerformPerforceOp( SimplePerforceOp, nCount, ppFullPathList, NULL );
  1947. }
  1948. void SimplePerforceOpCurChangeList( int nCount, const char **ppFullPathList, const char *pDescription )
  1949. {
  1950. if ( char const *szClNum = s_p4.GetOpenFileChangeListNum() )
  1951. {
  1952. CUtlVector< const char * > arrArgv;
  1953. arrArgv.SetCount( nCount + 3 );
  1954. arrArgv[0] = "-c";
  1955. arrArgv[1] = szClNum;
  1956. for ( int k = 0; k < nCount; ++ k )
  1957. arrArgv[ 2 + k ] = ppFullPathList[ k ];
  1958. arrArgv[ nCount + 2 ] = NULL;
  1959. s_p4.GetClientApi().SetArgv( nCount + 2, const_cast<char**>( arrArgv.Base() ) );
  1960. s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser );
  1961. }
  1962. else
  1963. {
  1964. s_p4.GetClientApi().SetArgv( nCount, const_cast<char**>( ppFullPathList ) );
  1965. s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser );
  1966. }
  1967. }
  1968. bool CP4::PerformPerforceOpCurChangeList( const char *pOperation, int nCount, const char **ppFullPathList )
  1969. {
  1970. if ( m_sChangeListName.Length() )
  1971. {
  1972. s_pOperation = pOperation;
  1973. return PerformPerforceOp( SimplePerforceOpCurChangeList, nCount, ppFullPathList, NULL );
  1974. }
  1975. else
  1976. {
  1977. return PerformPerforceOp( pOperation, nCount, ppFullPathList );
  1978. }
  1979. }
  1980. bool CP4::PerformPerforceOp( const char *pOperation, const char *pFullPath )
  1981. {
  1982. g_ErrorHandlerUser.ResetErrorState();
  1983. if ( !IsConnectedToServer() )
  1984. {
  1985. g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL );
  1986. return false;
  1987. }
  1988. CScopedFileClientSpec spec( pFullPath );
  1989. char *argv[] = { (char *)pFullPath, NULL };
  1990. m_Client.SetArgv( 1, argv );
  1991. m_Client.Run( pOperation, &g_ErrorHandlerUser );
  1992. return g_ErrorHandlerUser.GetErrorState() < E_WARN;
  1993. }
  1994. bool CP4::PerformPerforceOpCurChangeList( const char *pOperation, const char *pFullPath )
  1995. {
  1996. g_ErrorHandlerUser.ResetErrorState();
  1997. if ( !IsConnectedToServer() )
  1998. {
  1999. g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL );
  2000. return false;
  2001. }
  2002. CScopedFileClientSpec spec( pFullPath );
  2003. char *argv[] = { "-c", "", (char *)pFullPath, NULL }, **pArgv = argv;
  2004. int numArgv = 3;
  2005. if ( char const *szClNum = GetOpenFileChangeListNum() )
  2006. {
  2007. argv[ 1 ] = const_cast< char * >( szClNum );
  2008. }
  2009. else
  2010. {
  2011. pArgv += 2;
  2012. numArgv -= 2;
  2013. }
  2014. m_Client.SetArgv( numArgv, pArgv );
  2015. m_Client.Run( pOperation, &g_ErrorHandlerUser );
  2016. return g_ErrorHandlerUser.GetErrorState() < E_WARN;
  2017. }
  2018. bool CP4::OpenFilesForAdd( int nCount, const char **ppFullPathList )
  2019. {
  2020. return PerformPerforceOpCurChangeList( "add", nCount, ppFullPathList );
  2021. }
  2022. bool CP4::OpenFilesForEdit( int nCount, const char **ppFullPathList )
  2023. {
  2024. if ( !PerformPerforceOpCurChangeList( "edit", nCount, ppFullPathList ) )
  2025. return false;
  2026. return MakeFilesWritable( nCount, ppFullPathList );
  2027. }
  2028. bool CP4::OpenFilesForDelete( int nCount, const char **ppFullPathList )
  2029. {
  2030. return PerformPerforceOpCurChangeList( "delete", nCount, ppFullPathList );
  2031. }
  2032. bool CP4::RevertFiles( int nCount, const char **ppFullPathList )
  2033. {
  2034. return PerformPerforceOp( "revert", nCount, ppFullPathList );
  2035. }
  2036. void SubmitPerforceOp( int nCount, const char **ppFullPathList, const char *pDescription )
  2037. {
  2038. Assert( nCount > 0 );
  2039. if ( nCount <= 0 )
  2040. return;
  2041. // First, create a new changelist
  2042. g_ChangelistCreateUser.CreateChangelist( pDescription );
  2043. if ( g_ChangelistCreateUser.GetData().Count() == 0 )
  2044. {
  2045. g_ErrorHandlerUser.SetErrorString( "Failed to create changelist for submit operation", E_FATAL );
  2046. return;
  2047. }
  2048. int nChangeList = g_ChangelistCreateUser.GetData()[0];
  2049. // Next, move all files to that new changelist
  2050. char pBuf[128];
  2051. V_snprintf( pBuf, sizeof(pBuf), "%d", nChangeList );
  2052. char **ppArgv = (char **)_alloca( (nCount + 2) * sizeof(char*) );
  2053. ppArgv[0] = "-c";
  2054. ppArgv[1] = pBuf;
  2055. memcpy( &ppArgv[2], ppFullPathList, nCount * sizeof(char*) );
  2056. s_p4.GetClientApi().SetArgv( nCount+2, ppArgv );
  2057. s_p4.GetClientApi().Run( "reopen", &g_ErrorHandlerUser );
  2058. // Finally, submit that changelist
  2059. g_SubmitUser.Submit( nChangeList );
  2060. }
  2061. bool CP4::SubmitFiles( int nCount, const char **ppFullPathList, const char *pDescription )
  2062. {
  2063. return PerformPerforceOp( SubmitPerforceOp, nCount, ppFullPathList, pDescription );
  2064. }
  2065. //-----------------------------------------------------------------------------
  2066. // Opens a file in s_p4 win
  2067. //-----------------------------------------------------------------------------
  2068. void CP4::OpenFileInP4Win( const char *pFullPath )
  2069. {
  2070. if ( !IsConnectedToServer() )
  2071. return;
  2072. char pClientSpec[MAX_PATH];
  2073. char pSystem[512];
  2074. if ( GetClientSpecForFile( pFullPath, pClientSpec, sizeof(pClientSpec) ) )
  2075. {
  2076. V_snprintf( pSystem, sizeof(pSystem), "p4win -q -c %s -s %s", pClientSpec, pFullPath );
  2077. }
  2078. else
  2079. {
  2080. V_snprintf( pSystem, sizeof(pSystem), "p4win -q -s %s", pFullPath );
  2081. }
  2082. STARTUPINFO si;
  2083. PROCESS_INFORMATION pi;
  2084. ZeroMemory( &si, sizeof(si) );
  2085. si.cb = sizeof(si);
  2086. ZeroMemory( &pi, sizeof(pi) );
  2087. // Start the child process.
  2088. CreateProcess( NULL, // No module name (use command line).
  2089. pSystem, // Command line.
  2090. NULL, // Process handle not inheritable.
  2091. NULL, // Thread handle not inheritable.
  2092. FALSE, // Set handle inheritance to FALSE.
  2093. 0, // No creation flags.
  2094. NULL, // Use parent's environment block.
  2095. NULL, // Use parent's starting directory.
  2096. &si, // Pointer to STARTUPINFO structure.
  2097. &pi ); // Pointer to PROCESS_INFORMATION structure.
  2098. }
  2099. //-----------------------------------------------------------------------------
  2100. // Is this file in perforce?
  2101. //-----------------------------------------------------------------------------
  2102. bool CP4::IsFileInPerforce( const char *fullpath )
  2103. {
  2104. if ( !IsConnectedToServer() )
  2105. return false;
  2106. CScopedFileClientSpec spec( fullpath );
  2107. g_FileUser.RetrieveFile( fullpath );
  2108. if ( g_FileUser.GetData().Count() == 0 )
  2109. return false;
  2110. // Return deleted files as not being in perforce
  2111. return !g_FileUser.GetData()[0].m_bDeleted;
  2112. }
  2113. //-----------------------------------------------------------------------------
  2114. // Is this file opened for edit?
  2115. //-----------------------------------------------------------------------------
  2116. P4FileState_t CP4::GetFileState( const char *pFullPath )
  2117. {
  2118. if ( !IsConnectedToServer() )
  2119. return P4FILE_UNOPENED;
  2120. CScopedFileClientSpec spec( pFullPath );
  2121. g_FileUser.RetrieveOpenedFiles( pFullPath );
  2122. if ( g_FileUser.GetData().Count() == 0 )
  2123. return P4FILE_UNOPENED;
  2124. return g_FileUser.GetData()[0].m_eOpenState;
  2125. }
  2126. //-----------------------------------------------------------------------------
  2127. // Returns file information for a single file
  2128. //-----------------------------------------------------------------------------
  2129. bool CP4::GetFileInfo( const char *pFullPath, P4File_t *pFileInfo )
  2130. {
  2131. if ( !IsConnectedToServer() )
  2132. return false;
  2133. CScopedFileClientSpec spec( pFullPath );
  2134. g_FileUser.RetrieveFile( pFullPath );
  2135. if ( g_FileUser.GetData().Count() != 1 )
  2136. return false;
  2137. memcpy( pFileInfo, &g_FileUser.GetData()[0], sizeof(P4File_t) );
  2138. return true;
  2139. }
  2140. //-----------------------------------------------------------------------------
  2141. // Are we connected to the server? (and should we reconnect?)
  2142. //-----------------------------------------------------------------------------
  2143. bool CP4::IsConnectedToServer( bool bRetry )
  2144. {
  2145. if ( bRetry && m_bConnectedToServer )
  2146. {
  2147. // don't comment this back in until it's called less often to avoid spamming the server
  2148. // (currently, this is called every time the ifm file menu is opened)
  2149. // this means that we won't detect losing the server until after a failed Run() cmd
  2150. // -jd
  2151. // m_Client.Run( "monitor show" ); // do something to test if the server's still up
  2152. if ( m_Client.Dropped() )
  2153. {
  2154. Shutdown();
  2155. }
  2156. }
  2157. if ( !m_bConnectedToServer && bRetry )
  2158. {
  2159. Init();
  2160. }
  2161. return m_bConnectedToServer;
  2162. }
  2163. //-----------------------------------------------------------------------------
  2164. // retrieves the last error from the last op (which is likely to span multiple lines)
  2165. // this is only valid after OpenFile[s]For{Add,Edit,Delete} or {Submit,Revert}File[s]
  2166. //-----------------------------------------------------------------------------
  2167. const char *CP4::GetLastError()
  2168. {
  2169. return g_ErrorHandlerUser.GetErrorString();
  2170. }