aboutsummaryrefslogtreecommitdiff
path: root/mp/src/public/UtlCachedFileData.h
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/public/UtlCachedFileData.h
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/public/UtlCachedFileData.h')
-rw-r--r--mp/src/public/UtlCachedFileData.h1001
1 files changed, 1001 insertions, 0 deletions
diff --git a/mp/src/public/UtlCachedFileData.h b/mp/src/public/UtlCachedFileData.h
new file mode 100644
index 00000000..b1ced0a5
--- /dev/null
+++ b/mp/src/public/UtlCachedFileData.h
@@ -0,0 +1,1001 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#ifndef UTLCACHEDFILEDATA_H
+#define UTLCACHEDFILEDATA_H
+#if defined( WIN32 )
+#pragma once
+#endif
+
+#include "filesystem.h" // FileNameHandle_t
+#include "utlrbtree.h"
+#include "utlbuffer.h"
+#include "UtlSortVector.h"
+#include "tier1/strtools.h"
+
+#include "tier0/memdbgon.h"
+
+// If you change to serialization protocols, this must be bumped...
+#define UTL_CACHE_SYSTEM_VERSION 2
+
+#define UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO (long)-2
+
+// Cacheable types must derive from this and implement the appropriate methods...
+abstract_class IBaseCacheInfo
+{
+public:
+ virtual void Save( CUtlBuffer& buf ) = 0;
+ virtual void Restore( CUtlBuffer& buf ) = 0;
+
+ virtual void Rebuild( char const *filename ) = 0;
+};
+
+typedef unsigned int (*PFNCOMPUTECACHEMETACHECKSUM)( void );
+
+typedef enum
+{
+ UTL_CACHED_FILE_USE_TIMESTAMP = 0,
+ UTL_CACHED_FILE_USE_FILESIZE,
+} UtlCachedFileDataType_t;
+
+template <class T>
+class CUtlCachedFileData
+{
+public:
+ CUtlCachedFileData
+ (
+ char const *repositoryFileName,
+ int version,
+ PFNCOMPUTECACHEMETACHECKSUM checksumfunc = NULL,
+ UtlCachedFileDataType_t fileCheckType = UTL_CACHED_FILE_USE_TIMESTAMP,
+ bool nevercheckdisk = false,
+ bool readonly = false,
+ bool savemanifest = false
+ )
+ : m_Elements( 0, 0, FileNameHandleLessFunc ),
+ m_sRepositoryFileName( repositoryFileName ),
+ m_nVersion( version ),
+ m_pfnMetaChecksum( checksumfunc ),
+ m_bDirty( false ),
+ m_bInitialized( false ),
+ m_uCurrentMetaChecksum( 0u ),
+ m_fileCheckType( fileCheckType ),
+ m_bNeverCheckDisk( nevercheckdisk ),
+ m_bReadOnly( readonly ),
+ m_bSaveManifest( savemanifest )
+ {
+ Assert( !m_sRepositoryFileName.IsEmpty() );
+ }
+
+ virtual ~CUtlCachedFileData()
+ {
+ m_Elements.RemoveAll();
+ int c = m_Data.Count();
+ for ( int i = 0; i < c ; ++i )
+ {
+ delete m_Data[ i ];
+ }
+ m_Data.RemoveAll();
+ }
+
+ T* Get( char const *filename );
+ const T* Get( char const *filename ) const;
+
+ T* operator[]( int i );
+ const T* operator[]( int i ) const;
+
+ int Count() const;
+
+ void GetElementName( int i, char *buf, int buflen )
+ {
+ buf[ 0 ] = 0;
+ if ( !m_Elements.IsValidIndex( i ) )
+ return;
+
+ g_pFullFileSystem->String( m_Elements[ i ].handle, buf, buflen );
+ }
+
+ bool EntryExists( char const *filename ) const
+ {
+ ElementType_t element;
+ element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
+ int idx = m_Elements.Find( element );
+ return idx != m_Elements.InvalidIndex() ? true : false;
+ }
+
+ void SetElement( char const *name, long fileinfo, T* src )
+ {
+ SetDirty( true );
+
+ int idx = GetIndex( name );
+
+ Assert( idx != m_Elements.InvalidIndex() );
+
+ ElementType_t& e = m_Elements[ idx ];
+
+ CUtlBuffer buf( 0, 0, 0 );
+
+ Assert( e.dataIndex != m_Data.InvalidIndex() );
+
+ T *dest = m_Data[ e.dataIndex ];
+
+ Assert( dest );
+
+ // I suppose we could do an assignment operator, but this should save/restore the data element just fine for
+ // tool purposes
+ ((IBaseCacheInfo *)src)->Save( buf );
+ ((IBaseCacheInfo *)dest)->Restore( buf );
+
+ e.fileinfo = fileinfo;
+ if ( ( e.fileinfo == -1 ) &&
+ ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
+ {
+ e.fileinfo = 0;
+ }
+ // Force recheck
+ e.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO;
+ }
+
+ // 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
+ // will cause a rebuild...
+ bool IsUpToDate();
+
+ void Shutdown();
+ bool Init();
+
+ void Save();
+
+ void Reload();
+
+ void ForceRecheckDiskInfo();
+ // Iterates all entries and gets filesystem info and optionally causes rebuild on any existing items which are out of date
+ void CheckDiskInfo( bool force_rebuild, long cacheFileTime = 0L );
+
+ void SaveManifest();
+ bool ManifestExists();
+
+ const char *GetRepositoryFileName() const { return m_sRepositoryFileName; }
+
+ long GetFileInfo( char const *filename )
+ {
+ ElementType_t element;
+ element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
+ int idx = m_Elements.Find( element );
+ if ( idx == m_Elements.InvalidIndex() )
+ {
+ return 0L;
+ }
+
+ return m_Elements[ idx ].fileinfo;
+ }
+
+ int GetNumElements()
+ {
+ return m_Elements.Count();
+ }
+
+ bool IsDirty() const
+ {
+ return m_bDirty;
+ }
+
+ T *RebuildItem( const char *filename );
+
+private:
+
+ void InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile );
+ void InitLargeBuffer( FileHandle_t& fh, bool& deleteFile );
+
+ int GetIndex( const char *filename )
+ {
+ ElementType_t element;
+ element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
+ int idx = m_Elements.Find( element );
+ if ( idx == m_Elements.InvalidIndex() )
+ {
+ T *data = new T();
+
+ int dataIndex = m_Data.AddToTail( data );
+ idx = m_Elements.Insert( element );
+ m_Elements[ idx ].dataIndex = dataIndex;
+ }
+
+ return idx;
+ }
+
+ void CheckInit();
+
+ void SetDirty( bool dirty )
+ {
+ m_bDirty = dirty;
+ }
+
+ void RebuildCache( char const *filename, T *data );
+
+ struct ElementType_t
+ {
+ ElementType_t() :
+ handle( 0 ),
+ fileinfo( 0 ),
+ diskfileinfo( UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ),
+ dataIndex( -1 )
+ {
+ }
+
+ FileNameHandle_t handle;
+ long fileinfo;
+ long diskfileinfo;
+ int dataIndex;
+ };
+
+ static bool FileNameHandleLessFunc( ElementType_t const &lhs, ElementType_t const &rhs )
+ {
+ return lhs.handle < rhs.handle;
+ }
+
+ CUtlRBTree< ElementType_t > m_Elements;
+ CUtlVector< T * > m_Data;
+ CUtlString m_sRepositoryFileName;
+ int m_nVersion;
+ PFNCOMPUTECACHEMETACHECKSUM m_pfnMetaChecksum;
+ unsigned int m_uCurrentMetaChecksum;
+ UtlCachedFileDataType_t m_fileCheckType;
+ bool m_bNeverCheckDisk : 1;
+ bool m_bReadOnly : 1;
+ bool m_bSaveManifest : 1;
+ bool m_bDirty : 1;
+ bool m_bInitialized : 1;
+
+};
+
+
+template <class T>
+T* CUtlCachedFileData<T>::Get( char const *filename )
+{
+ int idx = GetIndex( filename );
+
+ ElementType_t& e = m_Elements[ idx ];
+
+ if ( e.fileinfo == -1 &&
+ m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
+ {
+ e.fileinfo = 0;
+ }
+ long cachefileinfo = e.fileinfo;
+ // Set the disk fileinfo the first time we encounter the filename
+ if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
+ {
+ if ( m_bNeverCheckDisk )
+ {
+ e.diskfileinfo = cachefileinfo;
+ }
+ else
+ {
+ if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
+ {
+ e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" );
+ // Missing files get a disk file size of 0
+ if ( e.diskfileinfo == -1 )
+ {
+ e.diskfileinfo = 0;
+ }
+ }
+ else
+ {
+ e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" );
+ }
+ }
+ }
+
+ Assert( e.dataIndex != m_Data.InvalidIndex() );
+
+ T *data = m_Data[ e.dataIndex ];
+
+ Assert( data );
+
+ // Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct...
+ if ( cachefileinfo != e.diskfileinfo )
+ {
+ if ( !m_bReadOnly )
+ {
+ RebuildCache( filename, data );
+ }
+ e.fileinfo = e.diskfileinfo;
+ }
+
+ return data;
+}
+
+template <class T>
+const T* CUtlCachedFileData<T>::Get( char const *filename ) const
+{
+ return const_cast< CUtlCachedFileData<T> * >(this)->Get( filename );
+}
+
+template <class T>
+T* CUtlCachedFileData<T>::operator[]( int i )
+{
+ return m_Data[ m_Elements[ i ].dataIndex ];
+}
+
+template <class T>
+const T* CUtlCachedFileData<T>::operator[]( int i ) const
+{
+ return m_Data[ m_Elements[ i ].dataIndex ];
+}
+
+template <class T>
+int CUtlCachedFileData<T>::Count() const
+{
+ return m_Elements.Count();
+}
+
+template <class T>
+void CUtlCachedFileData<T>::Reload()
+{
+ Shutdown();
+ Init();
+}
+
+template <class T>
+bool CUtlCachedFileData<T>::IsUpToDate()
+{
+ // Don't call Init/Shutdown if using this method!!!
+ Assert( !m_bInitialized );
+
+ if ( m_sRepositoryFileName.IsEmpty() )
+ {
+ Error( "CUtlCachedFileData: Can't IsUpToDate, no repository file specified." );
+ return false;
+ }
+
+ // Always compute meta checksum
+ m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0;
+
+ FileHandle_t fh;
+
+ fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" );
+ if ( fh == FILESYSTEM_INVALID_HANDLE )
+ {
+ return false;
+ }
+
+ // Version data is in first 12 bytes of file
+ byte header[ 12 ];
+ g_pFullFileSystem->Read( header, sizeof( header ), fh );
+ g_pFullFileSystem->Close( fh );
+
+ int cacheversion = *( int *)&header[ 0 ];
+
+ if ( UTL_CACHE_SYSTEM_VERSION != cacheversion )
+ {
+ DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
+ Assert( !m_bReadOnly );
+ if ( !m_bReadOnly )
+ {
+ g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
+ }
+ return false;
+ }
+
+ // Now parse data from the buffer
+ int version = *( int *)&header[ 4 ];
+ if ( version != m_nVersion )
+ {
+ DevMsg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
+ Assert( !m_bReadOnly );
+ if ( !m_bReadOnly )
+ {
+ g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
+ }
+ return false;
+ }
+
+ // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
+ // meta data function
+ unsigned int cache_meta_checksum = (unsigned int)*( int *)&header[ 8 ];
+
+ if ( cache_meta_checksum != m_uCurrentMetaChecksum )
+ {
+ DevMsg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
+ Assert( !m_bReadOnly );
+ if ( !m_bReadOnly )
+ {
+ g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
+ }
+ return false;
+ }
+
+ // Looks valid
+ return true;
+}
+
+template <class T>
+void CUtlCachedFileData<T>::InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile )
+{
+ deleteFile = false;
+
+ CUtlBuffer loadBuf;
+ g_pFullFileSystem->ReadToBuffer( fh, loadBuf );
+ g_pFullFileSystem->Close( fh );
+
+ int cacheversion = 0;
+ loadBuf.Get( &cacheversion, sizeof( cacheversion ) );
+
+ if ( UTL_CACHE_SYSTEM_VERSION == cacheversion )
+ {
+ // Now parse data from the buffer
+ int version = loadBuf.GetInt();
+
+ if ( version == m_nVersion )
+ {
+ // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
+ // meta data function
+ unsigned int cache_meta_checksum = loadBuf.GetInt();
+
+ if ( cache_meta_checksum == m_uCurrentMetaChecksum )
+ {
+ int count = loadBuf.GetInt();
+
+ Assert( count < 2000000 );
+
+ CUtlBuffer buf( 0, 0, 0 );
+
+ for ( int i = 0 ; i < count; ++i )
+ {
+ int bufsize = loadBuf.GetInt();
+ Assert( bufsize < 1000000 );
+
+ buf.Clear();
+ buf.EnsureCapacity( bufsize );
+
+ loadBuf.Get( buf.Base(), bufsize );
+
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, bufsize );
+
+ // Read the element name
+ char elementFileName[ 512 ];
+ buf.GetString( elementFileName, sizeof( elementFileName ) );
+
+ // Now read the element
+ int slot = GetIndex( elementFileName );
+
+ Assert( slot != m_Elements.InvalidIndex() );
+
+ ElementType_t& element = m_Elements[ slot ];
+
+ element.fileinfo = buf.GetInt();
+ if ( ( element.fileinfo == -1 ) &&
+ ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
+ {
+ element.fileinfo = 0;
+ }
+
+ Assert( element.dataIndex != m_Data.InvalidIndex() );
+
+ T *data = m_Data[ element.dataIndex ];
+
+ Assert( data );
+
+ ((IBaseCacheInfo *)data)->Restore( buf );
+ }
+ }
+ else
+ {
+ Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ }
+ }
+ else
+ {
+ Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ }
+ }
+ else
+ {
+ DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ }
+}
+
+template <class T>
+void CUtlCachedFileData<T>::InitLargeBuffer( FileHandle_t& fh, bool& deleteFile )
+{
+ deleteFile = false;
+
+ int cacheversion = 0;
+ g_pFullFileSystem->Read( &cacheversion, sizeof( cacheversion ), fh );
+
+ if ( UTL_CACHE_SYSTEM_VERSION == cacheversion )
+ {
+ // Now parse data from the buffer
+ int version = 0;
+ g_pFullFileSystem->Read( &version, sizeof( version ), fh );
+
+ if ( version == m_nVersion )
+ {
+ // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
+ // meta data function
+ unsigned int cache_meta_checksum = 0;
+
+ g_pFullFileSystem->Read( &cache_meta_checksum, sizeof( cache_meta_checksum ), fh );
+
+ if ( cache_meta_checksum == m_uCurrentMetaChecksum )
+ {
+ int count = 0;
+
+ g_pFullFileSystem->Read( &count, sizeof( count ), fh );
+
+ Assert( count < 2000000 );
+
+ CUtlBuffer buf( 0, 0, 0 );
+
+ for ( int i = 0 ; i < count; ++i )
+ {
+ int bufsize = 0;
+ g_pFullFileSystem->Read( &bufsize, sizeof( bufsize ), fh );
+
+ Assert( bufsize < 1000000 );
+ if ( bufsize > 1000000 )
+ {
+ Msg( "Discarding repository '%s' due to corruption\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ break;
+ }
+
+
+ buf.Clear();
+ buf.EnsureCapacity( bufsize );
+
+ int nBytesRead = g_pFullFileSystem->Read( buf.Base(), bufsize, fh );
+
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );
+
+ // Read the element name
+ char elementFileName[ 512 ];
+ buf.GetString( elementFileName, sizeof( elementFileName ) );
+
+ // Now read the element
+ int slot = GetIndex( elementFileName );
+
+ Assert( slot != m_Elements.InvalidIndex() );
+
+ ElementType_t& element = m_Elements[ slot ];
+
+ element.fileinfo = buf.GetInt();
+ if ( ( element.fileinfo == -1 ) &&
+ ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
+ {
+ element.fileinfo = 0;
+ }
+
+ Assert( element.dataIndex != m_Data.InvalidIndex() );
+
+ T *data = m_Data[ element.dataIndex ];
+
+ Assert( data );
+
+ ((IBaseCacheInfo *)data)->Restore( buf );
+ }
+ }
+ else
+ {
+ Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ }
+ }
+ else
+ {
+ Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ }
+ }
+ else
+ {
+ DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
+ deleteFile = true;
+ }
+
+ g_pFullFileSystem->Close( fh );
+}
+
+template <class T>
+bool CUtlCachedFileData<T>::Init()
+{
+ if ( m_bInitialized )
+ {
+ return true;
+ }
+
+ m_bInitialized = true;
+
+ if ( m_sRepositoryFileName.IsEmpty() )
+ {
+ Error( "CUtlCachedFileData: Can't Init, no repository file specified." );
+ return false;
+ }
+
+ // Always compute meta checksum
+ m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0;
+
+ FileHandle_t fh;
+
+ fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" );
+ if ( fh == FILESYSTEM_INVALID_HANDLE )
+ {
+ // Nothing on disk, we'll recreate everything from scratch...
+ SetDirty( true );
+ return true;
+ }
+ long fileTime = g_pFullFileSystem->GetFileTime( m_sRepositoryFileName, "MOD" );
+ int size = g_pFullFileSystem->Size( fh );
+
+ bool deletefile = false;
+
+ if ( size > 1024 * 1024 )
+ {
+ InitLargeBuffer( fh, deletefile );
+ }
+ else
+ {
+ InitSmallBuffer( fh, size, deletefile );
+ }
+
+ if ( deletefile )
+ {
+ Assert( !m_bReadOnly );
+ if ( !m_bReadOnly )
+ {
+ g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
+ }
+ SetDirty( true );
+ }
+ CheckDiskInfo( false, fileTime );
+ return true;
+}
+
+template <class T>
+void CUtlCachedFileData<T>::Save()
+{
+ char path[ 512 ];
+ Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) );
+ Q_StripFilename( path );
+
+ g_pFullFileSystem->CreateDirHierarchy( path, "MOD" );
+
+ if ( g_pFullFileSystem->FileExists( m_sRepositoryFileName, "MOD" ) &&
+ !g_pFullFileSystem->IsFileWritable( m_sRepositoryFileName, "MOD" ) )
+ {
+ g_pFullFileSystem->SetFileWritable( m_sRepositoryFileName, true, "MOD" );
+ }
+
+ // Now write to file
+ FileHandle_t fh;
+ fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "wb" );
+ if ( FILESYSTEM_INVALID_HANDLE == fh )
+ {
+ Warning( "Unable to persist cache '%s', check file permissions\n", m_sRepositoryFileName.String() );
+ }
+ else
+ {
+ SetDirty( false );
+
+ int v = UTL_CACHE_SYSTEM_VERSION;
+ g_pFullFileSystem->Write( &v, sizeof( v ), fh );
+ v = m_nVersion;
+ g_pFullFileSystem->Write( &v, sizeof( v ), fh );
+ v = (int)m_uCurrentMetaChecksum;
+ g_pFullFileSystem->Write( &v, sizeof( v ), fh );
+
+ // Element count
+ int c = Count();
+
+ g_pFullFileSystem->Write( &c, sizeof( c ), fh );
+
+ // Save repository back out to disk...
+ CUtlBuffer buf( 0, 0, 0 );
+
+ for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
+ {
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+
+ ElementType_t& element = m_Elements[ i ];
+
+ char fn[ 512 ];
+ g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
+
+ buf.PutString( fn );
+ buf.PutInt( element.fileinfo );
+
+ Assert( element.dataIndex != m_Data.InvalidIndex() );
+
+ T *data = m_Data[ element.dataIndex ];
+
+ Assert( data );
+
+ ((IBaseCacheInfo *)data)->Save( buf );
+
+ int bufsize = buf.TellPut();
+ g_pFullFileSystem->Write( &bufsize, sizeof( bufsize ), fh );
+ g_pFullFileSystem->Write( buf.Base(), bufsize, fh );
+ }
+
+ g_pFullFileSystem->Close( fh );
+ }
+
+ if ( m_bSaveManifest )
+ {
+ SaveManifest();
+ }
+}
+
+template <class T>
+void CUtlCachedFileData<T>::Shutdown()
+{
+ if ( !m_bInitialized )
+ return;
+
+ m_bInitialized = false;
+
+ if ( IsDirty() )
+ {
+ Save();
+ }
+ // No matter what, create the manifest if it doesn't exist on the HD yet
+ else if ( m_bSaveManifest && !ManifestExists() )
+ {
+ SaveManifest();
+ }
+
+ m_Elements.RemoveAll();
+}
+
+template <class T>
+bool CUtlCachedFileData<T>::ManifestExists()
+{
+ char manifest_name[ 512 ];
+ Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) );
+
+ Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) );
+
+ return g_pFullFileSystem->FileExists( manifest_name, "MOD" );
+}
+
+template <class T>
+void CUtlCachedFileData<T>::SaveManifest()
+{
+ // Save manifest out to disk...
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
+ {
+ ElementType_t& element = m_Elements[ i ];
+
+ char fn[ 512 ];
+ g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
+
+ buf.Printf( "\"%s\"\r\n", fn );
+ }
+
+ char path[ 512 ];
+ Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) );
+ Q_StripFilename( path );
+
+ g_pFullFileSystem->CreateDirHierarchy( path, "MOD" );
+
+ char manifest_name[ 512 ];
+ Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) );
+
+ Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) );
+
+ if ( g_pFullFileSystem->FileExists( manifest_name, "MOD" ) &&
+ !g_pFullFileSystem->IsFileWritable( manifest_name, "MOD" ) )
+ {
+ g_pFullFileSystem->SetFileWritable( manifest_name, true, "MOD" );
+ }
+
+ // Now write to file
+ FileHandle_t fh;
+ fh = g_pFullFileSystem->Open( manifest_name, "wb" );
+ if ( FILESYSTEM_INVALID_HANDLE != fh )
+ {
+ g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
+ g_pFullFileSystem->Close( fh );
+
+ // DevMsg( "Persisting cache manifest '%s' (%d entries)\n", manifest_name, c );
+ }
+ else
+ {
+ Warning( "Unable to persist cache manifest '%s', check file permissions\n", manifest_name );
+ }
+}
+
+template <class T>
+T *CUtlCachedFileData<T>::RebuildItem( const char *filename )
+{
+ int idx = GetIndex( filename );
+ ElementType_t& e = m_Elements[ idx ];
+
+ ForceRecheckDiskInfo();
+
+ long cachefileinfo = e.fileinfo;
+ // Set the disk fileinfo the first time we encounter the filename
+ if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
+ {
+ if ( m_bNeverCheckDisk )
+ {
+ e.diskfileinfo = cachefileinfo;
+ }
+ else
+ {
+ if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
+ {
+ e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" );
+ // Missing files get a disk file size of 0
+ if ( e.diskfileinfo == -1 )
+ {
+ e.diskfileinfo = 0;
+ }
+ }
+ else
+ {
+ e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" );
+ }
+ }
+ }
+
+ Assert( e.dataIndex != m_Data.InvalidIndex() );
+
+ T *data = m_Data[ e.dataIndex ];
+
+ Assert( data );
+
+ // Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct...
+ if ( !m_bReadOnly )
+ {
+ RebuildCache( filename, data );
+ }
+ e.fileinfo = e.diskfileinfo;
+
+ return data;
+}
+
+template <class T>
+void CUtlCachedFileData<T>::RebuildCache( char const *filename, T *data )
+{
+ Assert( !m_bReadOnly );
+
+ // Recache item, mark self as dirty
+ SetDirty( true );
+
+ ((IBaseCacheInfo *)data)->Rebuild( filename );
+}
+
+template <class T>
+void CUtlCachedFileData<T>::ForceRecheckDiskInfo()
+{
+ for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
+ {
+ ElementType_t& element = m_Elements[ i ];
+ element.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO;
+ }
+}
+
+class CSortedCacheFile
+{
+public:
+ FileNameHandle_t handle;
+ int index;
+
+ bool Less( const CSortedCacheFile &file0, const CSortedCacheFile &file1, void * )
+ {
+ char name0[ 512 ];
+ char name1[ 512 ];
+ g_pFullFileSystem->String( file0.handle, name0, sizeof( name0 ) );
+ g_pFullFileSystem->String( file1.handle, name1, sizeof( name1 ) );
+ return Q_stricmp( name0, name1 ) < 0 ? true : false;
+ }
+};
+
+// Iterates all entries and causes rebuild on any existing items which are out of date
+template <class T>
+void CUtlCachedFileData<T>::CheckDiskInfo( bool forcerebuild, long cacheFileTime )
+{
+ char fn[ 512 ];
+ int i;
+ if ( forcerebuild )
+ {
+ for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
+ {
+ ElementType_t& element = m_Elements[ i ];
+ g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
+ Get(fn);
+ }
+ return;
+ }
+
+ CUtlSortVector<CSortedCacheFile, CSortedCacheFile> list;
+ for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
+ {
+ ElementType_t& element = m_Elements[ i ];
+ CSortedCacheFile insert;
+ insert.handle = element.handle;
+ insert.index = i;
+ list.InsertNoSort( insert );
+ }
+ list.RedoSort();
+
+ if ( !list.Count() )
+ return;
+
+ bool bSteam = g_pFullFileSystem->IsSteam();
+
+ for ( int listStart = 0, listEnd = 0; listStart < list.Count(); listStart = listEnd+1 )
+ {
+ int pathIndex = g_pFullFileSystem->GetPathIndex( m_Elements[list[listStart].index].handle );
+ for ( listEnd = listStart; listEnd < list.Count(); listEnd++ )
+ {
+ ElementType_t& element = m_Elements[ list[listEnd].index ];
+
+ int pathTest = g_pFullFileSystem->GetPathIndex( element.handle );
+ if ( pathTest != pathIndex )
+ break;
+ }
+ g_pFullFileSystem->String( m_Elements[list[listStart].index].handle, fn, sizeof( fn ) );
+ Q_StripFilename( fn );
+ bool bCheck = true;
+
+ if ( m_bNeverCheckDisk )
+ {
+ bCheck = false;
+ }
+ else if ( !bSteam )
+ {
+ long pathTime = g_pFullFileSystem->GetPathTime( fn, "GAME" );
+ bCheck = (pathTime > cacheFileTime) ? true : false;
+ }
+
+ for ( i = listStart; i < listEnd; i++ )
+ {
+ ElementType_t& element = m_Elements[ list[i].index ];
+
+ if ( element.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
+ {
+ if ( !bCheck )
+ {
+ element.diskfileinfo = element.fileinfo;
+ }
+ else
+ {
+ g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
+ if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
+ {
+ element.diskfileinfo = g_pFullFileSystem->Size( fn, "GAME" );
+
+ // Missing files get a disk file size of 0
+ if ( element.diskfileinfo == -1 )
+ {
+ element.diskfileinfo = 0;
+ }
+ }
+ else
+ {
+ element.diskfileinfo = g_pFullFileSystem->GetFileTime( fn, "GAME" );
+ }
+ }
+ }
+ }
+ }
+}
+
+#include "tier0/memdbgoff.h"
+
+#endif // UTLCACHEDFILEDATA_H