902 lines
27 KiB
C++
902 lines
27 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
|
|
#ifndef UTLCACHEDFILEDATA_H
|
|
#define UTLCACHEDFILEDATA_H
|
|
#if defined(WIN32)
|
|
#pragma once
|
|
#endif
|
|
|
|
#include "UtlSortVector.h"
|
|
#include "filesystem.h" // FileNameHandle_t
|
|
#include "tier1/strtools.h"
|
|
#include "utlbuffer.h"
|
|
#include "utlrbtree.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);
|
|
|
|
// 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;
|
|
}
|
|
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) {
|
|
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,
|
|
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;
|
|
|
|
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 {
|
|
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
|