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.

1000 lines
24 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #ifndef UTLCACHEDFILEDATA_H
  9. #define UTLCACHEDFILEDATA_H
  10. #if defined( WIN32 )
  11. #pragma once
  12. #endif
  13. #include "filesystem.h" // FileNameHandle_t
  14. #include "utlrbtree.h"
  15. #include "utlbuffer.h"
  16. #include "UtlSortVector.h"
  17. #include "tier1/strtools.h"
  18. #include "tier0/memdbgon.h"
  19. // If you change to serialization protocols, this must be bumped...
  20. #define UTL_CACHE_SYSTEM_VERSION 2
  21. #define UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO (long)-2
  22. // Cacheable types must derive from this and implement the appropriate methods...
  23. abstract_class IBaseCacheInfo
  24. {
  25. public:
  26. virtual void Save( CUtlBuffer& buf ) = 0;
  27. virtual void Restore( CUtlBuffer& buf ) = 0;
  28. virtual void Rebuild( char const *filename ) = 0;
  29. };
  30. typedef unsigned int (*PFNCOMPUTECACHEMETACHECKSUM)( void );
  31. typedef enum
  32. {
  33. UTL_CACHED_FILE_USE_TIMESTAMP = 0,
  34. UTL_CACHED_FILE_USE_FILESIZE,
  35. } UtlCachedFileDataType_t;
  36. template <class T>
  37. class CUtlCachedFileData
  38. {
  39. public:
  40. CUtlCachedFileData
  41. (
  42. char const *repositoryFileName,
  43. int version,
  44. PFNCOMPUTECACHEMETACHECKSUM checksumfunc = NULL,
  45. UtlCachedFileDataType_t fileCheckType = UTL_CACHED_FILE_USE_TIMESTAMP,
  46. bool nevercheckdisk = false,
  47. bool readonly = false,
  48. bool savemanifest = false
  49. )
  50. : m_Elements( 0, 0, FileNameHandleLessFunc ),
  51. m_sRepositoryFileName( repositoryFileName ),
  52. m_nVersion( version ),
  53. m_pfnMetaChecksum( checksumfunc ),
  54. m_bDirty( false ),
  55. m_bInitialized( false ),
  56. m_uCurrentMetaChecksum( 0u ),
  57. m_fileCheckType( fileCheckType ),
  58. m_bNeverCheckDisk( nevercheckdisk ),
  59. m_bReadOnly( readonly ),
  60. m_bSaveManifest( savemanifest )
  61. {
  62. Assert( !m_sRepositoryFileName.IsEmpty() );
  63. }
  64. virtual ~CUtlCachedFileData()
  65. {
  66. m_Elements.RemoveAll();
  67. int c = m_Data.Count();
  68. for ( int i = 0; i < c ; ++i )
  69. {
  70. delete m_Data[ i ];
  71. }
  72. m_Data.RemoveAll();
  73. }
  74. // If bExpectMissing is set, don't complain if this causes synchronous disk I/O to build the cache.
  75. T* Get( char const *filename );
  76. const T* Get( char const *filename ) const;
  77. T* operator[]( int i );
  78. const T* operator[]( int i ) const;
  79. int Count() const;
  80. void GetElementName( int i, char *buf, int buflen )
  81. {
  82. buf[ 0 ] = 0;
  83. if ( !m_Elements.IsValidIndex( i ) )
  84. return;
  85. g_pFullFileSystem->String( m_Elements[ i ].handle, buf, buflen );
  86. }
  87. bool EntryExists( char const *filename ) const
  88. {
  89. ElementType_t element;
  90. element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
  91. int idx = m_Elements.Find( element );
  92. return idx != m_Elements.InvalidIndex() ? true : false;
  93. }
  94. void SetElement( char const *name, long fileinfo, T* src )
  95. {
  96. SetDirty( true );
  97. int idx = GetIndex( name );
  98. Assert( idx != m_Elements.InvalidIndex() );
  99. ElementType_t& e = m_Elements[ idx ];
  100. CUtlBuffer buf( 0, 0, 0 );
  101. Assert( e.dataIndex != m_Data.InvalidIndex() );
  102. T *dest = m_Data[ e.dataIndex ];
  103. Assert( dest );
  104. // I suppose we could do an assignment operator, but this should save/restore the data element just fine for
  105. // tool purposes
  106. ((IBaseCacheInfo *)src)->Save( buf );
  107. ((IBaseCacheInfo *)dest)->Restore( buf );
  108. e.fileinfo = fileinfo;
  109. if ( ( e.fileinfo == -1 ) &&
  110. ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
  111. {
  112. e.fileinfo = 0;
  113. }
  114. // Force recheck
  115. e.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO;
  116. }
  117. // If you create a cache and don't call init/shutdown, you can call this to do a quick check to see if the checksum/version
  118. // will cause a rebuild...
  119. bool IsUpToDate();
  120. void Shutdown();
  121. bool Init();
  122. void Save();
  123. void Reload();
  124. void ForceRecheckDiskInfo();
  125. // Iterates all entries and gets filesystem info and optionally causes rebuild on any existing items which are out of date
  126. void CheckDiskInfo( bool force_rebuild, long cacheFileTime = 0L );
  127. void SaveManifest();
  128. bool ManifestExists();
  129. const char *GetRepositoryFileName() const { return m_sRepositoryFileName; }
  130. long GetFileInfo( char const *filename )
  131. {
  132. ElementType_t element;
  133. element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
  134. int idx = m_Elements.Find( element );
  135. if ( idx == m_Elements.InvalidIndex() )
  136. {
  137. return 0L;
  138. }
  139. return m_Elements[ idx ].fileinfo;
  140. }
  141. int GetNumElements()
  142. {
  143. return m_Elements.Count();
  144. }
  145. bool IsDirty() const
  146. {
  147. return m_bDirty;
  148. }
  149. T *RebuildItem( const char *filename );
  150. private:
  151. void InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile );
  152. void InitLargeBuffer( FileHandle_t& fh, bool& deleteFile );
  153. int GetIndex( const char *filename )
  154. {
  155. ElementType_t element;
  156. element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
  157. int idx = m_Elements.Find( element );
  158. if ( idx == m_Elements.InvalidIndex() )
  159. {
  160. T *data = new T();
  161. int dataIndex = m_Data.AddToTail( data );
  162. idx = m_Elements.Insert( element );
  163. m_Elements[ idx ].dataIndex = dataIndex;
  164. }
  165. return idx;
  166. }
  167. void CheckInit();
  168. void SetDirty( bool dirty )
  169. {
  170. m_bDirty = dirty;
  171. }
  172. void RebuildCache( char const *filename, T *data );
  173. struct ElementType_t
  174. {
  175. ElementType_t() :
  176. handle( 0 ),
  177. fileinfo( 0 ),
  178. diskfileinfo( UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ),
  179. dataIndex( -1 )
  180. {
  181. }
  182. FileNameHandle_t handle;
  183. long fileinfo;
  184. long diskfileinfo;
  185. int dataIndex;
  186. };
  187. static bool FileNameHandleLessFunc( ElementType_t const &lhs, ElementType_t const &rhs )
  188. {
  189. return lhs.handle < rhs.handle;
  190. }
  191. CUtlRBTree< ElementType_t > m_Elements;
  192. CUtlVector< T * > m_Data;
  193. CUtlString m_sRepositoryFileName;
  194. int m_nVersion;
  195. PFNCOMPUTECACHEMETACHECKSUM m_pfnMetaChecksum;
  196. unsigned int m_uCurrentMetaChecksum;
  197. UtlCachedFileDataType_t m_fileCheckType;
  198. bool m_bNeverCheckDisk : 1;
  199. bool m_bReadOnly : 1;
  200. bool m_bSaveManifest : 1;
  201. bool m_bDirty : 1;
  202. bool m_bInitialized : 1;
  203. };
  204. template <class T>
  205. T* CUtlCachedFileData<T>::Get( char const *filename )
  206. {
  207. int idx = GetIndex( filename );
  208. ElementType_t& e = m_Elements[ idx ];
  209. if ( e.fileinfo == -1 &&
  210. m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
  211. {
  212. e.fileinfo = 0;
  213. }
  214. long cachefileinfo = e.fileinfo;
  215. // Set the disk fileinfo the first time we encounter the filename
  216. if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
  217. {
  218. if ( m_bNeverCheckDisk )
  219. {
  220. e.diskfileinfo = cachefileinfo;
  221. }
  222. else
  223. {
  224. if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
  225. {
  226. e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" );
  227. // Missing files get a disk file size of 0
  228. if ( e.diskfileinfo == -1 )
  229. {
  230. e.diskfileinfo = 0;
  231. }
  232. }
  233. else
  234. {
  235. e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" );
  236. }
  237. }
  238. }
  239. Assert( e.dataIndex != m_Data.InvalidIndex() );
  240. T *data = m_Data[ e.dataIndex ];
  241. Assert( data );
  242. // Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct...
  243. if ( cachefileinfo != e.diskfileinfo )
  244. {
  245. if ( !m_bReadOnly )
  246. {
  247. RebuildCache( filename, data );
  248. }
  249. e.fileinfo = e.diskfileinfo;
  250. }
  251. return data;
  252. }
  253. template <class T>
  254. const T* CUtlCachedFileData<T>::Get( char const *filename ) const
  255. {
  256. return const_cast< CUtlCachedFileData<T> * >(this)->Get( filename );
  257. }
  258. template <class T>
  259. T* CUtlCachedFileData<T>::operator[]( int i )
  260. {
  261. return m_Data[ m_Elements[ i ].dataIndex ];
  262. }
  263. template <class T>
  264. const T* CUtlCachedFileData<T>::operator[]( int i ) const
  265. {
  266. return m_Data[ m_Elements[ i ].dataIndex ];
  267. }
  268. template <class T>
  269. int CUtlCachedFileData<T>::Count() const
  270. {
  271. return m_Elements.Count();
  272. }
  273. template <class T>
  274. void CUtlCachedFileData<T>::Reload()
  275. {
  276. Shutdown();
  277. Init();
  278. }
  279. template <class T>
  280. bool CUtlCachedFileData<T>::IsUpToDate()
  281. {
  282. // Don't call Init/Shutdown if using this method!!!
  283. Assert( !m_bInitialized );
  284. if ( m_sRepositoryFileName.IsEmpty() )
  285. {
  286. Error( "CUtlCachedFileData: Can't IsUpToDate, no repository file specified." );
  287. return false;
  288. }
  289. // Always compute meta checksum
  290. m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0;
  291. FileHandle_t fh;
  292. fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" );
  293. if ( fh == FILESYSTEM_INVALID_HANDLE )
  294. {
  295. return false;
  296. }
  297. // Version data is in first 12 bytes of file
  298. byte header[ 12 ];
  299. g_pFullFileSystem->Read( header, sizeof( header ), fh );
  300. g_pFullFileSystem->Close( fh );
  301. int cacheversion = *( int *)&header[ 0 ];
  302. if ( UTL_CACHE_SYSTEM_VERSION != cacheversion )
  303. {
  304. DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
  305. Assert( !m_bReadOnly );
  306. if ( !m_bReadOnly )
  307. {
  308. g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
  309. }
  310. return false;
  311. }
  312. // Now parse data from the buffer
  313. int version = *( int *)&header[ 4 ];
  314. if ( version != m_nVersion )
  315. {
  316. DevMsg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
  317. Assert( !m_bReadOnly );
  318. if ( !m_bReadOnly )
  319. {
  320. g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
  321. }
  322. return false;
  323. }
  324. // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
  325. // meta data function
  326. unsigned int cache_meta_checksum = (unsigned int)*( int *)&header[ 8 ];
  327. if ( cache_meta_checksum != m_uCurrentMetaChecksum )
  328. {
  329. DevMsg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
  330. Assert( !m_bReadOnly );
  331. if ( !m_bReadOnly )
  332. {
  333. g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
  334. }
  335. return false;
  336. }
  337. // Looks valid
  338. return true;
  339. }
  340. template <class T>
  341. void CUtlCachedFileData<T>::InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile )
  342. {
  343. deleteFile = false;
  344. CUtlBuffer loadBuf;
  345. g_pFullFileSystem->ReadToBuffer( fh, loadBuf );
  346. g_pFullFileSystem->Close( fh );
  347. int cacheversion = 0;
  348. loadBuf.Get( &cacheversion, sizeof( cacheversion ) );
  349. if ( UTL_CACHE_SYSTEM_VERSION == cacheversion )
  350. {
  351. // Now parse data from the buffer
  352. int version = loadBuf.GetInt();
  353. if ( version == m_nVersion )
  354. {
  355. // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
  356. // meta data function
  357. unsigned int cache_meta_checksum = loadBuf.GetInt();
  358. if ( cache_meta_checksum == m_uCurrentMetaChecksum )
  359. {
  360. int count = loadBuf.GetInt();
  361. Assert( count < 2000000 );
  362. CUtlBuffer buf( 0, 0, 0 );
  363. for ( int i = 0 ; i < count; ++i )
  364. {
  365. int bufsize = loadBuf.GetInt();
  366. Assert( bufsize < 1000000 );
  367. buf.Clear();
  368. buf.EnsureCapacity( bufsize );
  369. loadBuf.Get( buf.Base(), bufsize );
  370. buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  371. buf.SeekPut( CUtlBuffer::SEEK_HEAD, bufsize );
  372. // Read the element name
  373. char elementFileName[ 512 ];
  374. buf.GetString( elementFileName );
  375. // Now read the element
  376. int slot = GetIndex( elementFileName );
  377. Assert( slot != m_Elements.InvalidIndex() );
  378. ElementType_t& element = m_Elements[ slot ];
  379. element.fileinfo = buf.GetInt();
  380. if ( ( element.fileinfo == -1 ) &&
  381. ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
  382. {
  383. element.fileinfo = 0;
  384. }
  385. Assert( element.dataIndex != m_Data.InvalidIndex() );
  386. T *data = m_Data[ element.dataIndex ];
  387. Assert( data );
  388. ((IBaseCacheInfo *)data)->Restore( buf );
  389. }
  390. }
  391. else
  392. {
  393. Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
  394. deleteFile = true;
  395. }
  396. }
  397. else
  398. {
  399. Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
  400. deleteFile = true;
  401. }
  402. }
  403. else
  404. {
  405. DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
  406. deleteFile = true;
  407. }
  408. }
  409. template <class T>
  410. void CUtlCachedFileData<T>::InitLargeBuffer( FileHandle_t& fh, bool& deleteFile )
  411. {
  412. deleteFile = false;
  413. int cacheversion = 0;
  414. g_pFullFileSystem->Read( &cacheversion, sizeof( cacheversion ), fh );
  415. if ( UTL_CACHE_SYSTEM_VERSION == cacheversion )
  416. {
  417. // Now parse data from the buffer
  418. int version = 0;
  419. g_pFullFileSystem->Read( &version, sizeof( version ), fh );
  420. if ( version == m_nVersion )
  421. {
  422. // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
  423. // meta data function
  424. unsigned int cache_meta_checksum = 0;
  425. g_pFullFileSystem->Read( &cache_meta_checksum, sizeof( cache_meta_checksum ), fh );
  426. if ( cache_meta_checksum == m_uCurrentMetaChecksum )
  427. {
  428. int count = 0;
  429. g_pFullFileSystem->Read( &count, sizeof( count ), fh );
  430. Assert( count < 2000000 );
  431. CUtlBuffer buf( 0, 0, 0 );
  432. for ( int i = 0 ; i < count; ++i )
  433. {
  434. int bufsize = 0;
  435. g_pFullFileSystem->Read( &bufsize, sizeof( bufsize ), fh );
  436. Assert( bufsize < 1000000 );
  437. if ( bufsize > 1000000 )
  438. {
  439. Msg( "Discarding repository '%s' due to corruption\n", m_sRepositoryFileName.String() );
  440. deleteFile = true;
  441. break;
  442. }
  443. buf.Clear();
  444. buf.EnsureCapacity( bufsize );
  445. int nBytesRead = g_pFullFileSystem->Read( buf.Base(), bufsize, fh );
  446. buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  447. buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );
  448. // Read the element name
  449. char elementFileName[ 512 ];
  450. buf.GetString( elementFileName );
  451. // Now read the element
  452. int slot = GetIndex( elementFileName );
  453. Assert( slot != m_Elements.InvalidIndex() );
  454. ElementType_t& element = m_Elements[ slot ];
  455. element.fileinfo = buf.GetInt();
  456. if ( ( element.fileinfo == -1 ) &&
  457. ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
  458. {
  459. element.fileinfo = 0;
  460. }
  461. Assert( element.dataIndex != m_Data.InvalidIndex() );
  462. T *data = m_Data[ element.dataIndex ];
  463. Assert( data );
  464. ((IBaseCacheInfo *)data)->Restore( buf );
  465. }
  466. }
  467. else
  468. {
  469. Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
  470. deleteFile = true;
  471. }
  472. }
  473. else
  474. {
  475. Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
  476. deleteFile = true;
  477. }
  478. }
  479. else
  480. {
  481. DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
  482. deleteFile = true;
  483. }
  484. g_pFullFileSystem->Close( fh );
  485. }
  486. template <class T>
  487. bool CUtlCachedFileData<T>::Init()
  488. {
  489. if ( m_bInitialized )
  490. {
  491. return true;
  492. }
  493. m_bInitialized = true;
  494. if ( m_sRepositoryFileName.IsEmpty() )
  495. {
  496. Error( "CUtlCachedFileData: Can't Init, no repository file specified." );
  497. return false;
  498. }
  499. // Always compute meta checksum
  500. m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0;
  501. FileHandle_t fh;
  502. fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" );
  503. if ( fh == FILESYSTEM_INVALID_HANDLE )
  504. {
  505. // Nothing on disk, we'll recreate everything from scratch...
  506. SetDirty( true );
  507. return true;
  508. }
  509. long fileTime = g_pFullFileSystem->GetFileTime( m_sRepositoryFileName, "MOD" );
  510. int size = g_pFullFileSystem->Size( fh );
  511. bool deletefile = false;
  512. if ( size > 1024 * 1024 )
  513. {
  514. InitLargeBuffer( fh, deletefile );
  515. }
  516. else
  517. {
  518. InitSmallBuffer( fh, size, deletefile );
  519. }
  520. if ( deletefile )
  521. {
  522. Assert( !m_bReadOnly );
  523. if ( !m_bReadOnly )
  524. {
  525. g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
  526. }
  527. SetDirty( true );
  528. }
  529. CheckDiskInfo( false, fileTime );
  530. return true;
  531. }
  532. template <class T>
  533. void CUtlCachedFileData<T>::Save()
  534. {
  535. char path[ 512 ];
  536. Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) );
  537. Q_StripFilename( path );
  538. g_pFullFileSystem->CreateDirHierarchy( path, "MOD" );
  539. if ( g_pFullFileSystem->FileExists( m_sRepositoryFileName, "MOD" ) &&
  540. !g_pFullFileSystem->IsFileWritable( m_sRepositoryFileName, "MOD" ) )
  541. {
  542. g_pFullFileSystem->SetFileWritable( m_sRepositoryFileName, true, "MOD" );
  543. }
  544. // Now write to file
  545. FileHandle_t fh;
  546. fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "wb" );
  547. if ( FILESYSTEM_INVALID_HANDLE == fh )
  548. {
  549. ExecuteNTimes( 25, Warning( "Unable to persist cache '%s', check file permissions\n", m_sRepositoryFileName.String() ) );
  550. }
  551. else
  552. {
  553. SetDirty( false );
  554. int v = UTL_CACHE_SYSTEM_VERSION;
  555. g_pFullFileSystem->Write( &v, sizeof( v ), fh );
  556. v = m_nVersion;
  557. g_pFullFileSystem->Write( &v, sizeof( v ), fh );
  558. v = (int)m_uCurrentMetaChecksum;
  559. g_pFullFileSystem->Write( &v, sizeof( v ), fh );
  560. // Element count
  561. int c = Count();
  562. g_pFullFileSystem->Write( &c, sizeof( c ), fh );
  563. // Save repository back out to disk...
  564. CUtlBuffer buf( 0, 0, 0 );
  565. for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
  566. {
  567. buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  568. ElementType_t& element = m_Elements[ i ];
  569. char fn[ 512 ];
  570. g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
  571. buf.PutString( fn );
  572. buf.PutInt( element.fileinfo );
  573. Assert( element.dataIndex != m_Data.InvalidIndex() );
  574. T *data = m_Data[ element.dataIndex ];
  575. Assert( data );
  576. ((IBaseCacheInfo *)data)->Save( buf );
  577. int bufsize = buf.TellPut();
  578. g_pFullFileSystem->Write( &bufsize, sizeof( bufsize ), fh );
  579. g_pFullFileSystem->Write( buf.Base(), bufsize, fh );
  580. }
  581. g_pFullFileSystem->Close( fh );
  582. }
  583. if ( m_bSaveManifest )
  584. {
  585. SaveManifest();
  586. }
  587. }
  588. template <class T>
  589. void CUtlCachedFileData<T>::Shutdown()
  590. {
  591. if ( !m_bInitialized )
  592. return;
  593. m_bInitialized = false;
  594. if ( IsDirty() )
  595. {
  596. Save();
  597. }
  598. // No matter what, create the manifest if it doesn't exist on the HD yet
  599. else if ( m_bSaveManifest && !ManifestExists() )
  600. {
  601. SaveManifest();
  602. }
  603. m_Elements.RemoveAll();
  604. }
  605. template <class T>
  606. bool CUtlCachedFileData<T>::ManifestExists()
  607. {
  608. char manifest_name[ 512 ];
  609. Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) );
  610. Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) );
  611. return g_pFullFileSystem->FileExists( manifest_name, "MOD" );
  612. }
  613. template <class T>
  614. void CUtlCachedFileData<T>::SaveManifest()
  615. {
  616. // Save manifest out to disk...
  617. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  618. for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
  619. {
  620. ElementType_t& element = m_Elements[ i ];
  621. char fn[ 512 ];
  622. g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
  623. buf.Printf( "\"%s\"\r\n", fn );
  624. }
  625. char path[ 512 ];
  626. Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) );
  627. Q_StripFilename( path );
  628. g_pFullFileSystem->CreateDirHierarchy( path, "MOD" );
  629. char manifest_name[ 512 ];
  630. Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) );
  631. Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) );
  632. if ( g_pFullFileSystem->FileExists( manifest_name, "MOD" ) &&
  633. !g_pFullFileSystem->IsFileWritable( manifest_name, "MOD" ) )
  634. {
  635. g_pFullFileSystem->SetFileWritable( manifest_name, true, "MOD" );
  636. }
  637. // Now write to file
  638. FileHandle_t fh;
  639. fh = g_pFullFileSystem->Open( manifest_name, "wb" );
  640. if ( FILESYSTEM_INVALID_HANDLE != fh )
  641. {
  642. g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
  643. g_pFullFileSystem->Close( fh );
  644. // DevMsg( "Persisting cache manifest '%s' (%d entries)\n", manifest_name, c );
  645. }
  646. else
  647. {
  648. Warning( "Unable to persist cache manifest '%s', check file permissions\n", manifest_name );
  649. }
  650. }
  651. template <class T>
  652. T *CUtlCachedFileData<T>::RebuildItem( const char *filename )
  653. {
  654. int idx = GetIndex( filename );
  655. ElementType_t& e = m_Elements[ idx ];
  656. ForceRecheckDiskInfo();
  657. long cachefileinfo = e.fileinfo;
  658. // Set the disk fileinfo the first time we encounter the filename
  659. if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
  660. {
  661. if ( m_bNeverCheckDisk )
  662. {
  663. e.diskfileinfo = cachefileinfo;
  664. }
  665. else
  666. {
  667. if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
  668. {
  669. e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" );
  670. // Missing files get a disk file size of 0
  671. if ( e.diskfileinfo == -1 )
  672. {
  673. e.diskfileinfo = 0;
  674. }
  675. }
  676. else
  677. {
  678. e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" );
  679. }
  680. }
  681. }
  682. Assert( e.dataIndex != m_Data.InvalidIndex() );
  683. T *data = m_Data[ e.dataIndex ];
  684. Assert( data );
  685. // Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct...
  686. if ( !m_bReadOnly )
  687. {
  688. RebuildCache( filename, data );
  689. }
  690. e.fileinfo = e.diskfileinfo;
  691. return data;
  692. }
  693. template <class T>
  694. void CUtlCachedFileData<T>::RebuildCache( char const *filename, T *data )
  695. {
  696. Assert( !m_bReadOnly );
  697. // Recache item, mark self as dirty
  698. SetDirty( true );
  699. ((IBaseCacheInfo *)data)->Rebuild( filename );
  700. }
  701. template <class T>
  702. void CUtlCachedFileData<T>::ForceRecheckDiskInfo()
  703. {
  704. for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
  705. {
  706. ElementType_t& element = m_Elements[ i ];
  707. element.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO;
  708. }
  709. }
  710. class CSortedCacheFile
  711. {
  712. public:
  713. FileNameHandle_t handle;
  714. int index;
  715. bool Less( const CSortedCacheFile &file0, const CSortedCacheFile &file1, void * )
  716. {
  717. char name0[ 512 ];
  718. char name1[ 512 ];
  719. g_pFullFileSystem->String( file0.handle, name0, sizeof( name0 ) );
  720. g_pFullFileSystem->String( file1.handle, name1, sizeof( name1 ) );
  721. return Q_stricmp( name0, name1 ) < 0 ? true : false;
  722. }
  723. };
  724. // Iterates all entries and causes rebuild on any existing items which are out of date
  725. template <class T>
  726. void CUtlCachedFileData<T>::CheckDiskInfo( bool forcerebuild, long cacheFileTime )
  727. {
  728. char fn[ 512 ];
  729. int i;
  730. if ( forcerebuild )
  731. {
  732. for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
  733. {
  734. ElementType_t& element = m_Elements[ i ];
  735. g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
  736. Get( fn );
  737. }
  738. return;
  739. }
  740. CUtlSortVector<CSortedCacheFile, CSortedCacheFile> list;
  741. for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
  742. {
  743. ElementType_t& element = m_Elements[ i ];
  744. CSortedCacheFile insert;
  745. insert.handle = element.handle;
  746. insert.index = i;
  747. list.InsertNoSort( insert );
  748. }
  749. list.RedoSort();
  750. if ( !list.Count() )
  751. return;
  752. for ( int listStart = 0, listEnd = 0; listStart < list.Count(); listStart = listEnd+1 )
  753. {
  754. int pathIndex = g_pFullFileSystem->GetPathIndex( m_Elements[list[listStart].index].handle );
  755. for ( listEnd = listStart; listEnd < list.Count(); listEnd++ )
  756. {
  757. ElementType_t& element = m_Elements[ list[listEnd].index ];
  758. int pathTest = g_pFullFileSystem->GetPathIndex( element.handle );
  759. if ( pathTest != pathIndex )
  760. break;
  761. }
  762. g_pFullFileSystem->String( m_Elements[list[listStart].index].handle, fn, sizeof( fn ) );
  763. Q_StripFilename( fn );
  764. bool bCheck = true;
  765. if ( m_bNeverCheckDisk )
  766. {
  767. bCheck = false;
  768. }
  769. else
  770. {
  771. long pathTime = g_pFullFileSystem->GetPathTime( fn, "GAME" );
  772. bCheck = (pathTime > cacheFileTime) ? true : false;
  773. }
  774. for ( i = listStart; i < listEnd; i++ )
  775. {
  776. ElementType_t& element = m_Elements[ list[i].index ];
  777. if ( element.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
  778. {
  779. if ( !bCheck )
  780. {
  781. element.diskfileinfo = element.fileinfo;
  782. }
  783. else
  784. {
  785. g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
  786. if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
  787. {
  788. element.diskfileinfo = g_pFullFileSystem->Size( fn, "GAME" );
  789. // Missing files get a disk file size of 0
  790. if ( element.diskfileinfo == -1 )
  791. {
  792. element.diskfileinfo = 0;
  793. }
  794. }
  795. else
  796. {
  797. element.diskfileinfo = g_pFullFileSystem->GetFileTime( fn, "GAME" );
  798. }
  799. }
  800. }
  801. }
  802. }
  803. }
  804. #include "tier0/memdbgoff.h"
  805. #endif // UTLCACHEDFILEDATA_H