1001 lines
24 KiB
C++
1001 lines
24 KiB
C++
//========= 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
|