mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-11 19:29:08 +00:00
1
This commit is contained in:
@@ -0,0 +1,782 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
//=======================================================================================//
|
||||
|
||||
#include "../utils/bzip2/bzlib.h"
|
||||
#include "sv_filepublish.h"
|
||||
#include "utlstring.h"
|
||||
#include "strtools.h"
|
||||
#include "sv_replaycontext.h"
|
||||
#include "convar.h"
|
||||
#include "fmtstr.h"
|
||||
#include "compression.h"
|
||||
#include "replay/shared_defs.h"
|
||||
#include "spew.h"
|
||||
#include "utlqueue.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
ConVar replay_publish_simulate_delay_local_http( "replay_publish_simulate_delay_local_http", "0", FCVAR_DONTRECORD,
|
||||
"Simulate a delay (in seconds) when publishing replay data via local HTTP.", true, 0.0f, true, 60.0f );
|
||||
ConVar replay_publish_simulate_rename_fail( "replay_publish_simulate_rename_fail", "0", FCVAR_DONTRECORD,
|
||||
"Simulate a rename failure during local HTTP publishing, which will force a manual copy & delete.", true, 0.0f, true, 1.0f );
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CBasePublishJob::CBasePublishJob( JobPriority_t nPriority/*=JP_NORMAL*/,
|
||||
ISpewer *pSpewer/*=g_pDefaultSpewer*/ )
|
||||
: CBaseJob( nPriority, pSpewer )
|
||||
{
|
||||
}
|
||||
|
||||
void CBasePublishJob::SimulateDelay( int nDelay, const char *pThreadName )
|
||||
{
|
||||
if ( nDelay > 0 )
|
||||
{
|
||||
Log( "%s thread: Simulating %i sec delay.\n", pThreadName, nDelay );
|
||||
ThreadSleep( nDelay * 1000 );
|
||||
Log( "%s thread: simulation done.\n", pThreadName );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CLocalPublishJob::CLocalPublishJob( const char *pLocalFilename )
|
||||
{
|
||||
V_strcpy( m_szLocalFilename, pLocalFilename );
|
||||
}
|
||||
|
||||
JobStatus_t CLocalPublishJob::DoExecute()
|
||||
{
|
||||
DBG( "Attempting to rename file to local fileserver path..." );
|
||||
|
||||
PrintEventStartMsg( "Source file exists?" );
|
||||
if ( !g_pFullFileSystem->FileExists( m_szLocalFilename ) )
|
||||
{
|
||||
PrintEventResult( false );
|
||||
CFmtStr fmtError( "Source file '%s' does not exist", m_szLocalFilename );
|
||||
SetError( ERROR_SOURCE_FILE_DOES_NOT_EXIST, fmtError.Access() );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
PrintEventResult( true );
|
||||
|
||||
// Make sure the publish path exists
|
||||
const char *pFileserverPath = g_pServerReplayContext->GetLocalFileServerPath();
|
||||
PrintEventStartMsg( "Checking fileserver path" );
|
||||
if ( !g_pFullFileSystem->IsDirectory( pFileserverPath ) )
|
||||
{
|
||||
PrintEventResult( false );
|
||||
CFmtStr fmtError( "Fileserver path '%s' invalid (see replay_local_fileserver_path)",
|
||||
pFileserverPath );
|
||||
SetError( ERROR_INVALID_FILESERVER_PATH, fmtError.Access() );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
PrintEventResult( true );
|
||||
|
||||
// Format a path & filename that points to the fileserver's download directory, with <session name>.dmx on the end
|
||||
const char *pFilename = V_UnqualifiedFileName( m_szLocalFilename );
|
||||
CFmtStr fmtPublishFilename( "%s%s", pFileserverPath, pFilename );
|
||||
const char *pTargetFilename = fmtPublishFilename.Access();
|
||||
|
||||
// Delete the destination file if it exists already
|
||||
if ( g_pFullFileSystem->FileExists( pTargetFilename ) )
|
||||
{
|
||||
PrintEventStartMsg( "Target file exists - deleting" );
|
||||
g_pFullFileSystem->RemoveFile( pTargetFilename );
|
||||
|
||||
// Give the system a bit of time before another check
|
||||
ThreadSleep( 500 );
|
||||
|
||||
if ( g_pFullFileSystem->FileExists( pTargetFilename ) )
|
||||
{
|
||||
#ifdef WIN32
|
||||
LPVOID pMsgBuf;
|
||||
if ( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
||||
(LPTSTR) &pMsgBuf,
|
||||
0,
|
||||
NULL ))
|
||||
{
|
||||
Log( "\n\nError: %s\n", (const char *)pMsgBuf );
|
||||
LocalFree( pMsgBuf );
|
||||
}
|
||||
#endif
|
||||
PrintEventResult( false );
|
||||
CFmtStr fmtError( "Target already existed and could not be removed: '%s'", pTargetFilename );
|
||||
SetError( ERROR_COULD_NOT_DELETE_TARGET_FILE, fmtError.Access() );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
PrintEventResult( true );
|
||||
}
|
||||
|
||||
// Simulate a delay if necessary
|
||||
SimulateDelay( replay_publish_simulate_delay_local_http.GetInt(), "Local HTTP" );
|
||||
|
||||
// Rename the file - RenameFile() still returns true, even if the destination pathname
|
||||
// is nonsense. If the *source* is invalid, it fails as expected, though. Adding a FileExists()
|
||||
// does not help.
|
||||
PrintEventStartMsg( "Renaming to target" );
|
||||
const bool bSimulateRenameFail = replay_publish_simulate_rename_fail.GetBool();
|
||||
if ( bSimulateRenameFail || !g_pFullFileSystem->RenameFile( m_szLocalFilename, pTargetFilename ) )
|
||||
{
|
||||
// Try to explicitly copy to target
|
||||
if ( g_pEngine->CopyFile( m_szLocalFilename, pTargetFilename ) )
|
||||
{
|
||||
// ...and deletion of source.
|
||||
g_pFullFileSystem->RemoveFile( m_szLocalFilename );
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintEventResult( false );
|
||||
CFmtStr fmtError( "Failed to rename '%s' -> '%s'\n", m_szLocalFilename, pTargetFilename );
|
||||
SetError( ERROR_RENAME_FAILED, fmtError.Access() );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
PrintEventResult( true );
|
||||
DBG( "Rename succeeded.\n" );
|
||||
return JOB_OK;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CLocalPublishJob *SV_CreateLocalPublishJob( const char *pLocalFilename )
|
||||
{
|
||||
return new CLocalPublishJob( pLocalFilename );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CCompressionJob::CCompressionJob( const uint8 *pSrcData, uint32 nSrcSize, CompressorType_t nType,
|
||||
bool *pOutResult, uint32 *pResultSize )
|
||||
: m_pSrcData( pSrcData ),
|
||||
m_nSrcSize( nSrcSize ),
|
||||
m_pCompressionResult( pOutResult ),
|
||||
m_pResultSize( pResultSize )
|
||||
{
|
||||
*m_pCompressionResult = false;
|
||||
*m_pResultSize = 0;
|
||||
|
||||
m_pCompressor = CreateCompressor( nType );
|
||||
}
|
||||
|
||||
JobStatus_t CCompressionJob::DoExecute()
|
||||
{
|
||||
IF_REPLAY_DBG2( Warning( "Attempting to compress...\n" ) );
|
||||
|
||||
if ( m_nSrcSize == 0 )
|
||||
{
|
||||
SetError( ERROR_FAILED_ZERO_LENGTH_DATA, "Compression failed. Zero length data." );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
|
||||
int nResult = JOB_FAILED;
|
||||
|
||||
// Attempt to compress the file
|
||||
const int nMaxCompressedSize = ceil( m_nSrcSize * 1.1f ) + 600; // see "destLen" - http://www.bzip.org/1.0.3/html/util-fns.html
|
||||
uint8 *pCompressed = new uint8[ nMaxCompressedSize ];
|
||||
|
||||
// Compress
|
||||
unsigned int nCompressedSize;
|
||||
PrintEventStartMsg( "Compressing" );
|
||||
if ( !m_pCompressor->Compress( (char *)pCompressed, &nCompressedSize, (const char *)m_pSrcData, m_nSrcSize ) )
|
||||
{
|
||||
// Compression failed?
|
||||
IF_REPLAY_DBG2( Warning( "Could not compress stream.\n" ) );
|
||||
PrintEventResult( false );
|
||||
SetError( ERROR_OK_COULDNOTCOMPRESS );
|
||||
|
||||
// Set result to uncompressed buffer and free compressed
|
||||
m_pResult = (uint8 *)m_pSrcData;
|
||||
delete [] pCompressed;
|
||||
|
||||
*m_pCompressionResult = false;
|
||||
*m_pResultSize = m_nSrcSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintEventResult( true );
|
||||
|
||||
// Success!
|
||||
DBG( "Compression succeeded.\n" );
|
||||
|
||||
nResult = JOB_OK;
|
||||
|
||||
// Set result to compressed buffer
|
||||
m_pResult = pCompressed;
|
||||
|
||||
*m_pResultSize = nCompressedSize;
|
||||
*m_pCompressionResult = true;
|
||||
}
|
||||
|
||||
// Compression would have been worse than not compressing at all
|
||||
return nResult;
|
||||
}
|
||||
|
||||
void CCompressionJob::GetOutputData( uint8 **ppData, uint32 *pDataSize ) const
|
||||
{
|
||||
*ppData = m_pResult;
|
||||
*pDataSize = *m_pResultSize;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CMd5Job::CMd5Job( const void *pSrcData, int nSrcSize, bool *pOutHashed, uint8 *pOutHash,
|
||||
unsigned int pSeed[4]/*=NULL*/ )
|
||||
: m_pSrcData( pSrcData ),
|
||||
m_nSrcSize( nSrcSize ),
|
||||
m_pHashed( pOutHashed ),
|
||||
m_pHash( pOutHash ),
|
||||
m_pSeed( pSeed )
|
||||
{
|
||||
*m_pHashed = false;
|
||||
V_memset( pOutHash, 0, 16 );
|
||||
}
|
||||
|
||||
JobStatus_t CMd5Job::DoExecute()
|
||||
{
|
||||
IF_REPLAY_DBG2( Warning( "Attempting to hash...\n" ) );
|
||||
|
||||
PrintEventStartMsg( "Running" );
|
||||
bool bResult = g_pEngine->MD5_HashBuffer( m_pHash, (const uint8 *)m_pSrcData, m_nSrcSize, m_pSeed );
|
||||
PrintEventResult( bResult );
|
||||
*m_pHashed = bResult;
|
||||
|
||||
if ( !bResult )
|
||||
return JOB_FAILED;
|
||||
|
||||
IF_REPLAY_DBG2( Warning( "Hash succeeded\n" ) );
|
||||
return JOB_OK;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CDeleteLocalFileJob::CDeleteLocalFileJob( const char *pFilename )
|
||||
{
|
||||
V_strncpy( m_szFilename, pFilename, sizeof( m_szFilename ) - 1 );
|
||||
}
|
||||
|
||||
JobStatus_t CDeleteLocalFileJob::DoExecute()
|
||||
{
|
||||
// File exists?
|
||||
if ( !g_pFullFileSystem->FileExists( m_szFilename ) )
|
||||
{
|
||||
SetError( ERROR_FILE_DOES_NOT_EXISTS );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
|
||||
// Attempt to remove the file now
|
||||
g_pFullFileSystem->RemoveFile( m_szFilename );
|
||||
|
||||
// Delete succeeded?
|
||||
if ( g_pFullFileSystem->FileExists( m_szFilename ) )
|
||||
{
|
||||
SetError( ERROR_COULD_NOT_DELETE );
|
||||
return JOB_FAILED;
|
||||
}
|
||||
|
||||
return JOB_OK;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
class CBaseFilePublisher : public IFilePublisher
|
||||
{
|
||||
public:
|
||||
enum Phase_t
|
||||
{
|
||||
PHASE_INVALID = -1,
|
||||
|
||||
PHASE_COMPRESSION,
|
||||
PHASE_HASH,
|
||||
PHASE_ADJUSTHEADER,
|
||||
PHASE_WRITETODISK,
|
||||
PHASE_PUBLISH,
|
||||
PHASE_DELETEFILE,
|
||||
|
||||
NUM_PHASES
|
||||
};
|
||||
|
||||
CBaseFilePublisher()
|
||||
: m_pCallbackHandler( NULL ),
|
||||
m_pUserData( NULL ),
|
||||
m_pCurrentJob( NULL ),
|
||||
m_pInData( NULL ),
|
||||
m_pHeaderData( NULL ),
|
||||
m_nStatus( PUBLISHSTATUS_INVALID ),
|
||||
m_nPhase( PHASE_INVALID ),
|
||||
m_bCompressedOk( false ),
|
||||
m_bHashedOk( false ),
|
||||
m_nHeaderSize( 0 ),
|
||||
m_nCompressedSize( 0 ),
|
||||
m_nInSize( 0 ),
|
||||
m_nInType( IO_INVALID )
|
||||
{
|
||||
m_szOutFilename[ 0 ] = 0;
|
||||
V_memset( m_aHash, 0, sizeof( m_aHash ) );
|
||||
}
|
||||
|
||||
virtual PublishStatus_t GetStatus() const
|
||||
{
|
||||
return m_nStatus;
|
||||
}
|
||||
|
||||
void SetStatus( PublishStatus_t nStatus )
|
||||
{
|
||||
m_nStatus = nStatus;
|
||||
}
|
||||
|
||||
virtual bool IsDone() const
|
||||
{
|
||||
return m_nStatus != PUBLISHSTATUS_INVALID;
|
||||
}
|
||||
|
||||
virtual bool Compressed() const
|
||||
{
|
||||
return m_bCompressedOk;
|
||||
}
|
||||
|
||||
virtual bool Hashed() const
|
||||
{
|
||||
return m_bHashedOk;
|
||||
}
|
||||
|
||||
virtual void GetHash( uint8 *pOut ) const
|
||||
{
|
||||
V_memcpy( pOut, m_aHash, sizeof( m_aHash ) );
|
||||
}
|
||||
|
||||
virtual CompressorType_t GetCompressorType() const
|
||||
{
|
||||
return m_bCompressedOk ? m_nCompressorType : COMPRESSORTYPE_INVALID;
|
||||
}
|
||||
|
||||
virtual int GetCompressedSize() const
|
||||
{
|
||||
return m_nCompressedSize;
|
||||
}
|
||||
|
||||
virtual void AbortAndCleanup()
|
||||
{
|
||||
if ( m_pCurrentJob )
|
||||
{
|
||||
m_pCurrentJob->Abort( true );
|
||||
m_pCurrentJob = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void FinishSynchronouslyAndCleanup()
|
||||
{
|
||||
if ( m_pCurrentJob )
|
||||
{
|
||||
m_pCurrentJob->WaitForFinishAndRelease();
|
||||
m_pCurrentJob = NULL;
|
||||
}
|
||||
|
||||
SetStatus( PUBLISHSTATUS_ABORTED );
|
||||
}
|
||||
|
||||
virtual void Publish( const PublishFileParams_t ¶ms )
|
||||
{
|
||||
V_strcpy( m_szOutFilename, params.m_pOutFilename );
|
||||
|
||||
m_pInData = params.m_pSrcData;
|
||||
m_nInSize = params.m_nSrcSize;
|
||||
m_pCallbackHandler = params.m_pCallbackHandler;
|
||||
m_pUserData = params.m_pUserData;
|
||||
m_bFreeSrcData = params.m_bFreeSrcData;
|
||||
m_pSrcData = params.m_pSrcData; // Cache src data so we can determine whether free'ing is OK
|
||||
m_pHeaderData = params.m_pHeaderData;
|
||||
m_nHeaderSize = params.m_nHeaderSize;
|
||||
|
||||
m_flStartTime = g_pEngine->GetHostTime();
|
||||
|
||||
if ( params.m_nCompressorType != COMPRESSORTYPE_INVALID )
|
||||
{
|
||||
m_PhaseQueue.Insert( PHASE_COMPRESSION );
|
||||
m_nCompressorType = params.m_nCompressorType; // Cache compressor type
|
||||
}
|
||||
|
||||
if ( params.m_bHash )
|
||||
{
|
||||
m_PhaseQueue.Insert( PHASE_HASH );
|
||||
}
|
||||
|
||||
if ( params.m_pHeaderData )
|
||||
{
|
||||
Assert( params.m_nHeaderSize );
|
||||
m_PhaseQueue.Insert( PHASE_ADJUSTHEADER );
|
||||
}
|
||||
|
||||
m_PhaseQueue.Insert( PHASE_WRITETODISK );
|
||||
m_PhaseQueue.Insert( PHASE_PUBLISH );
|
||||
|
||||
if ( params.m_bDeleteFile )
|
||||
{
|
||||
m_PhaseQueue.Insert( PHASE_DELETEFILE );
|
||||
}
|
||||
|
||||
// Start off first job
|
||||
SetupNextJob( true );
|
||||
}
|
||||
|
||||
void PrintErrors()
|
||||
{
|
||||
// If we don't print out any error now, it'll be lost once the job is released. Kind of a hack.
|
||||
if ( m_pCurrentJob->GetStatus() == JOB_FAILED && !IsFailureOkForPhase() )
|
||||
{
|
||||
CBasePublishJob *pCurrentJob = dynamic_cast< CBasePublishJob * >( m_pCurrentJob );
|
||||
if ( pCurrentJob )
|
||||
{
|
||||
g_pBlockSpewer->PrintBlockStart();
|
||||
g_pBlockSpewer->PrintEventError( pCurrentJob->GetErrorStr() );
|
||||
g_pBlockSpewer->PrintBlockEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Abort()
|
||||
{
|
||||
// Abort the job
|
||||
if ( m_pCurrentJob )
|
||||
{
|
||||
m_pCurrentJob->Abort( true );
|
||||
m_pCurrentJob = NULL;
|
||||
}
|
||||
|
||||
// Update status
|
||||
SetStatus( PUBLISHSTATUS_ABORTED );
|
||||
|
||||
// Let owner know we've aborted
|
||||
if ( m_pCallbackHandler )
|
||||
{
|
||||
m_pCallbackHandler->OnPublishAborted( this );
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Think()
|
||||
{
|
||||
const float flCurTime = g_pEngine->GetHostTime();
|
||||
extern ConVar replay_fileserver_offload_aborttime;
|
||||
if ( flCurTime > m_flStartTime + replay_fileserver_offload_aborttime.GetFloat() )
|
||||
{
|
||||
g_pBlockSpewer->PrintMsg( Replay_va( "ERROR: Publish timed out after %i seconds.", replay_fileserver_offload_aborttime.GetInt() ) );
|
||||
Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !m_pCurrentJob )
|
||||
return;
|
||||
|
||||
const int nJobStatus = m_pCurrentJob->GetStatus();
|
||||
if ( nJobStatus <= JOB_OK )
|
||||
{
|
||||
PrintErrors();
|
||||
|
||||
// What it says
|
||||
CacheOutputsOfCurrentJobForInputsOfNextJob();
|
||||
|
||||
// Job's done - clean up
|
||||
m_pCurrentJob->Release();
|
||||
m_pCurrentJob = NULL;
|
||||
|
||||
// Did the current job fail?
|
||||
bool bPublishDone = false;
|
||||
if ( nJobStatus < JOB_OK && !IsFailureOkForPhase() )
|
||||
{
|
||||
// Don't process the next job
|
||||
SetStatus( PUBLISHSTATUS_FAILED );
|
||||
bPublishDone = true;
|
||||
}
|
||||
else if ( IsLastPhase() )
|
||||
{
|
||||
// nJobStatus is JOB_OK and we are in publish phase.
|
||||
SetStatus( PUBLISHSTATUS_OK );
|
||||
bPublishDone = true;
|
||||
}
|
||||
|
||||
if ( bPublishDone )
|
||||
{
|
||||
InvokeCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, publish isn't complete yet - go to next phase and spawn job thread
|
||||
SetupNextJob( false );
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual CBasePublishJob *GetPublishJob() const = 0;
|
||||
|
||||
char m_szOutFilename[MAX_OSPATH]; // Filename only
|
||||
IPublishCallbackHandler *m_pCallbackHandler;
|
||||
void *m_pUserData;
|
||||
|
||||
private:
|
||||
enum IO_t
|
||||
{
|
||||
IO_INVALID = -1,
|
||||
IO_BUFFER,
|
||||
IO_FILE,
|
||||
IO_DONTCARE, // As an input, this means the job doesn't care about the main pipeline stream
|
||||
// (e.g. adjust header gets its inputs elsewhere) phase. As an output, this
|
||||
// should only be used for the final phase (publish).
|
||||
};
|
||||
|
||||
void CacheOutputsOfCurrentJobForInputsOfNextJob()
|
||||
{
|
||||
bool bFreeOldInData = false;
|
||||
uint8 *pOldInData = m_pInData;
|
||||
|
||||
IO_t nOutputType = GetCurrentPhaseOutputType();
|
||||
|
||||
// Write phase is a special case
|
||||
if ( m_nPhase == PHASE_WRITETODISK )
|
||||
{
|
||||
// Clear the in buffer
|
||||
m_pInData = NULL;
|
||||
m_nInSize = 0;
|
||||
|
||||
bFreeOldInData = true;
|
||||
}
|
||||
else if ( nOutputType == IO_BUFFER )
|
||||
{
|
||||
// This should always be a CBasePublishJob
|
||||
CBasePublishJob *pCurrentJob = dynamic_cast< CBasePublishJob * >( m_pCurrentJob );
|
||||
Assert( pCurrentJob );
|
||||
|
||||
// Get job output buffer
|
||||
uint8 *pJobOutData;
|
||||
uint32 nJobOutDataSize;
|
||||
pCurrentJob->GetOutputData( &pJobOutData, &nJobOutDataSize );
|
||||
|
||||
// Compare output data against input data - if different, free input and replace
|
||||
// with output. In the case of hashing, for example, the input buffer is used
|
||||
// to do some computation, but the buffer itself goes untouched.
|
||||
if ( pJobOutData && ( m_pInData != pJobOutData || m_nInSize != nJobOutDataSize ) )
|
||||
{
|
||||
m_pInData = pJobOutData;
|
||||
m_nInSize = nJobOutDataSize;
|
||||
bFreeOldInData = true;
|
||||
}
|
||||
}
|
||||
else if ( nOutputType == IO_DONTCARE )
|
||||
{
|
||||
// This should have been cleaned up in write-to-disk phase if we're in publish phase
|
||||
Assert( m_nPhase != PHASE_PUBLISH || m_pInData == NULL );
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
else
|
||||
{
|
||||
AssertMsg( 0, "Shouldn't reach here" );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Free old input data?
|
||||
if ( bFreeOldInData && ( m_bFreeSrcData || pOldInData != m_pSrcData ) )
|
||||
{
|
||||
delete [] pOldInData;
|
||||
}
|
||||
|
||||
// Cache output of current job for input of next job
|
||||
if ( m_nPhase != PHASE_PUBLISH )
|
||||
{
|
||||
m_nInType = nOutputType;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This needs to return a CJob ptr (i.e. and not a CBaseJob) since the job may be an AsyncWrite
|
||||
CJob *GetJobForPhase( Phase_t nPhase )
|
||||
{
|
||||
CJob *pResult = NULL;
|
||||
|
||||
switch ( nPhase )
|
||||
{
|
||||
case PHASE_COMPRESSION:
|
||||
pResult = new CCompressionJob( m_pInData, m_nInSize, m_nCompressorType, &m_bCompressedOk, &m_nCompressedSize );
|
||||
break;
|
||||
|
||||
case PHASE_HASH:
|
||||
pResult = new CMd5Job( m_pInData, m_nInSize, &m_bHashedOk, m_aHash );
|
||||
break;
|
||||
|
||||
case PHASE_ADJUSTHEADER:
|
||||
{
|
||||
// Let the callback handler make any adjustments to the header (add md5 digest, etc.)
|
||||
m_pCallbackHandler->AdjustHeader( this, m_pHeaderData );
|
||||
|
||||
if ( m_pHeaderData && m_nHeaderSize )
|
||||
{
|
||||
// Write the header to the target file
|
||||
FSAsyncControl_t hFileJob;
|
||||
const bool bFreeMemory = false;
|
||||
g_pFullFileSystem->AsyncWrite( m_szOutFilename, m_pHeaderData, m_nHeaderSize, bFreeMemory, false, &hFileJob );
|
||||
pResult = (CJob *)hFileJob;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PHASE_WRITETODISK:
|
||||
if ( m_pInData && m_nInSize )
|
||||
{
|
||||
// Create an asynchronous write job - if a header already exists in the file, append.
|
||||
FSAsyncControl_t hFileJob;
|
||||
const bool bAppend = m_pHeaderData != NULL;
|
||||
g_pFullFileSystem->AsyncWrite( m_szOutFilename, m_pInData, m_nInSize, false, bAppend, &hFileJob );
|
||||
pResult = (CJob *)hFileJob;
|
||||
}
|
||||
break;
|
||||
|
||||
case PHASE_PUBLISH:
|
||||
pResult = GetPublishJob();
|
||||
break;
|
||||
|
||||
case PHASE_DELETEFILE:
|
||||
pResult = new CDeleteLocalFileJob( m_szOutFilename );
|
||||
break;
|
||||
|
||||
default:
|
||||
AssertMsg( 0, "File publish phase is bad." );
|
||||
}
|
||||
|
||||
// Sanity check input type with output type of previous job
|
||||
Assert(
|
||||
GetCurrentPhaseInputType() == IO_DONTCARE ||
|
||||
m_nInType == IO_DONTCARE ||
|
||||
GetCurrentPhaseInputType() == m_nInType
|
||||
);
|
||||
|
||||
return pResult;
|
||||
}
|
||||
|
||||
bool IsFailureOkForPhase() const
|
||||
{
|
||||
// Compression will fail (e.g. due to small buffer size), which shouldn't bring down the house.
|
||||
return m_nPhase == PHASE_COMPRESSION || m_nPhase == PHASE_DELETEFILE;
|
||||
}
|
||||
|
||||
bool IsLastPhase() const
|
||||
{
|
||||
return m_PhaseQueue.Count() == 0;
|
||||
}
|
||||
|
||||
IO_t GetCurrentPhaseInputType() const
|
||||
{
|
||||
return sm_aPhaseIOTypes[ m_nPhase ].m_nInputType;
|
||||
}
|
||||
|
||||
IO_t GetCurrentPhaseOutputType() const
|
||||
{
|
||||
return sm_aPhaseIOTypes[ m_nPhase ].m_nOutputType;
|
||||
}
|
||||
|
||||
void SetupNextJob( bool bFirstJob )
|
||||
{
|
||||
// Get next phase from queue
|
||||
Assert( m_PhaseQueue.Count() > 0 );
|
||||
m_nPhase = ( Phase_t )m_PhaseQueue.RemoveAtHead();
|
||||
|
||||
// Set the input type if this is the first job
|
||||
if ( bFirstJob )
|
||||
{
|
||||
m_nInType = GetCurrentPhaseInputType();
|
||||
}
|
||||
|
||||
// Create the job
|
||||
m_pCurrentJob = GetJobForPhase( m_nPhase );
|
||||
|
||||
// Kick off the job now
|
||||
SV_GetThreadPool()->AddJob( m_pCurrentJob );
|
||||
}
|
||||
|
||||
void InvokeCallback()
|
||||
{
|
||||
if ( m_pCallbackHandler )
|
||||
{
|
||||
m_pCallbackHandler->OnPublishComplete( this, m_pUserData );
|
||||
}
|
||||
}
|
||||
|
||||
CUtlQueue< uint8 > m_PhaseQueue;
|
||||
bool m_bCompressedOk;
|
||||
bool m_bHashedOk;
|
||||
CompressorType_t m_nCompressorType;
|
||||
uint8 m_aHash[16];
|
||||
Phase_t m_nPhase;
|
||||
PublishStatus_t m_nStatus;
|
||||
CJob *m_pCurrentJob;
|
||||
uint32 m_nCompressedSize;
|
||||
|
||||
IO_t m_nInType;
|
||||
uint8 *m_pInData;
|
||||
uint32 m_nInSize;
|
||||
|
||||
bool m_bFreeSrcData;
|
||||
void *m_pSrcData;
|
||||
|
||||
void *m_pHeaderData;
|
||||
int m_nHeaderSize;
|
||||
|
||||
float m_flStartTime;
|
||||
|
||||
struct IoInfo_t
|
||||
{
|
||||
IO_t m_nInputType;
|
||||
IO_t m_nOutputType;
|
||||
};
|
||||
|
||||
static IoInfo_t sm_aPhaseIOTypes[ NUM_PHASES ];
|
||||
};
|
||||
|
||||
CBaseFilePublisher::IoInfo_t CBaseFilePublisher::sm_aPhaseIOTypes[ NUM_PHASES ] =
|
||||
{
|
||||
// Input Output
|
||||
{ IO_BUFFER, IO_BUFFER }, // PHASE_COMPRESSION
|
||||
{ IO_BUFFER, IO_BUFFER }, // PHASE_HASH
|
||||
{ IO_DONTCARE, IO_DONTCARE }, // PHASE_ADJUSTHEADER - this phase can operate independent of the pipeline, so
|
||||
// long as any compression/hashing is taken care of.
|
||||
{ IO_BUFFER, IO_FILE }, // PHASE_WRITETODISK
|
||||
{ IO_FILE, IO_DONTCARE }, // PHASE_PUBLISH
|
||||
{ IO_DONTCARE, IO_DONTCARE } // PHASE_DELETEFILE
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
class CLocalFileserverPublisher : public CBaseFilePublisher
|
||||
{
|
||||
typedef CBaseFilePublisher BaseClass;
|
||||
public:
|
||||
virtual CBasePublishJob *GetPublishJob() const
|
||||
{
|
||||
DBG( "Attempting to publish a file locally...\n" );
|
||||
|
||||
// Destination filename is implied
|
||||
return new CLocalPublishJob( m_szOutFilename );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
IFilePublisher *SV_PublishFile( const PublishFileParams_t ¶ms )
|
||||
{
|
||||
Assert( !params.m_pHeaderData || ( params.m_pHeaderData && params.m_pCallbackHandler ) );
|
||||
|
||||
IFilePublisher *pResult;
|
||||
|
||||
pResult = new CLocalFileserverPublisher();
|
||||
|
||||
pResult->Publish( params );
|
||||
|
||||
return pResult;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user