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
1000 lines
24 KiB
//========= 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(); |
|
} |
|
|
|
// If bExpectMissing is set, don't complain if this causes synchronous disk I/O to build the cache. |
|
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, time_t 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 long fileinfo; |
|
long 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 ); |
|
|
|
// 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 ); |
|
|
|
// 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; |
|
} |
|
time_t 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 ) |
|
{ |
|
ExecuteNTimes( 25, 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, time_t 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; |
|
|
|
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 |
|
{ |
|
time_t 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
|
|
|