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.
331 lines
10 KiB
331 lines
10 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
//=======================================================================================// |
|
|
|
#include "cl_sessionblockdownloader.h" |
|
#include "replay/ienginereplay.h" |
|
#include "cl_recordingsessionblockmanager.h" |
|
#include "cl_replaycontext.h" |
|
#include "cl_recordingsession.h" |
|
#include "cl_recordingsessionblock.h" |
|
#include "errorsystem.h" |
|
#include "convar.h" |
|
#include "vprof.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
extern IEngineReplay *g_pEngine; |
|
extern ConVar replay_maxconcurrentdownloads; |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
int CSessionBlockDownloader::sm_nNumCurrentDownloads = 0; |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
CSessionBlockDownloader::CSessionBlockDownloader() |
|
: m_nMaxBlock( -1 ) |
|
{ |
|
} |
|
|
|
void CSessionBlockDownloader::Shutdown() |
|
{ |
|
AbortDownloadsAndCleanup( NULL ); |
|
|
|
Assert( sm_nNumCurrentDownloads == 0 ); |
|
} |
|
|
|
void CSessionBlockDownloader::AbortDownloadsAndCleanup( CClientRecordingSession *pSession ) |
|
{ |
|
// NOTE: sm_nNumCurrentDownloads will be decremented in OnDownloadComplete(), which is |
|
// invoked by CHttpDownloader::AbortDownloadAndCleanup() |
|
|
|
// Abort any remaining downloads - callbacks will be invoked, so this shutdown |
|
// should be called before any of those objects are cleaned up. |
|
FOR_EACH_LL( m_lstDownloaders, i ) |
|
{ |
|
CHttpDownloader *pCurDownloader = m_lstDownloaders[ i ]; |
|
|
|
// If a session was passed in, make sure it has the same handle as the block |
|
CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pCurDownloader->GetUserData(); |
|
if ( pSession && ( !pBlock || pBlock->m_hSession != pSession->GetHandle() ) ) |
|
continue; |
|
|
|
pCurDownloader->AbortDownloadAndCleanup(); |
|
delete pCurDownloader; |
|
} |
|
m_lstDownloaders.RemoveAll(); |
|
} |
|
|
|
bool CSessionBlockDownloader::AtMaxConcurrentDownloads() const |
|
{ |
|
return ( sm_nNumCurrentDownloads >= replay_maxconcurrentdownloads.GetInt() ); |
|
} |
|
|
|
float CSessionBlockDownloader::GetNextThinkTime() const |
|
{ |
|
return g_pEngine->GetHostTime() + 0.5f; |
|
} |
|
|
|
void CSessionBlockDownloader::Think() |
|
{ |
|
VPROF_BUDGET( "CSessionBlockDownloader::Think", VPROF_BUDGETGROUP_REPLAY ); |
|
|
|
CBaseThinker::Think(); |
|
|
|
// Hack to not think right away |
|
if ( g_pEngine->GetHostTime() < 3 ) |
|
return; |
|
|
|
// Don't go over the desired maximum # of concurrent downloads |
|
if ( !AtMaxConcurrentDownloads() ) |
|
{ |
|
// Go through all blocks and begin downloading any that the server index downloader |
|
// has determined are ready |
|
CClientRecordingSessionBlockManager *pBlockManager = CL_GetRecordingSessionBlockManager(); |
|
FOR_EACH_OBJ( pBlockManager, i ) |
|
{ |
|
CClientRecordingSessionBlock *pBlock = CL_CastBlock( pBlockManager->m_vecObjs[ i ] ); |
|
|
|
// Checks to see if the remote status is marked as ready for download |
|
if ( !pBlock->ShouldDownloadNow() ) |
|
continue; |
|
|
|
// Lookup the session for the block |
|
CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->Find( pBlock->m_hSession ) ); |
|
if ( !pSession ) |
|
{ |
|
AssertMsg( 0, "Session for block not found! This should never happen!" ); |
|
continue; |
|
} |
|
|
|
// Do we need any blocks at all from this session? Is this block within range? |
|
int iLastBlockToDownload = pSession->GetLastBlockToDownload(); |
|
if ( iLastBlockToDownload < 0 || pBlock->m_iReconstruction > iLastBlockToDownload ) |
|
{ |
|
continue; |
|
} |
|
|
|
// Begin the download |
|
CHttpDownloader *pDownloader = new CHttpDownloader( this ); |
|
const char *pFilename = V_UnqualifiedFileName( pBlock->m_szFullFilename ); |
|
#ifdef _DEBUG |
|
extern ConVar replay_forcedownloadurl; |
|
const char *pForceURL = replay_forcedownloadurl.GetString(); |
|
const char *pURL = pForceURL[0] ? pForceURL : Replay_va( "%s%s", pSession->m_strBaseDownloadURL.Get(), pFilename ); |
|
#else |
|
const char *pURL = Replay_va( "%s%s", pSession->m_strBaseDownloadURL.Get(), pFilename ); |
|
#endif |
|
const char *pGamePath = Replay_va( "%s%s", CL_GetRecordingSessionBlockManager()->GetSavePath(), pFilename ); |
|
pDownloader->BeginDownload( pURL, pGamePath, (void *)pBlock, &pBlock->m_uBytesDownloaded ); |
|
|
|
IF_REPLAY_DBG( |
|
Warning ( "%s block %i from %s to path %s...\n", |
|
pBlock->GetNumDownloadAttempts() ? "RETRYING download for" : "Downloading" , |
|
pBlock->m_iReconstruction, pURL, pGamePath ) |
|
); |
|
|
|
// Add the downloader |
|
m_lstDownloaders.AddToTail( pDownloader ); |
|
|
|
// Update block's status |
|
pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADING; |
|
|
|
// Mark as dirty |
|
CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false ); |
|
|
|
// Update # of concurrent downloads |
|
++sm_nNumCurrentDownloads; |
|
|
|
// Get out if we're at max downloads now |
|
if ( AtMaxConcurrentDownloads() ) |
|
break; |
|
} |
|
} |
|
|
|
int it = m_lstDownloaders.Head(); |
|
while ( it != m_lstDownloaders.InvalidIndex() ) |
|
{ |
|
// Remove finished downloaders |
|
CHttpDownloader *pCurDownloader = m_lstDownloaders[ it ]; |
|
if ( pCurDownloader->IsDone() && pCurDownloader->CanDelete() ) |
|
{ |
|
int itRemove = it; |
|
|
|
// Next |
|
it = m_lstDownloaders.Next( it ); |
|
|
|
// Remove the downloader from the list |
|
m_lstDownloaders.Remove( itRemove ); |
|
|
|
// Free the downloader |
|
delete pCurDownloader; |
|
} |
|
else |
|
{ |
|
// Let the downloader think |
|
pCurDownloader->Think(); |
|
|
|
// Next |
|
it = m_lstDownloaders.Next( it ); |
|
} |
|
} |
|
} |
|
|
|
void CSessionBlockDownloader::OnConnecting( CHttpDownloader *pDownloader ) |
|
{ |
|
CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock ); |
|
pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_CONNECTING; |
|
} |
|
|
|
void CSessionBlockDownloader::OnFetch( CHttpDownloader *pDownloader ) |
|
{ |
|
CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock ); |
|
pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADING; |
|
} |
|
|
|
void CSessionBlockDownloader::OnDownloadComplete( CHttpDownloader *pDownloader, const unsigned char *pData ) |
|
{ |
|
// TODO: Compare downloaded byte size (pDownloader->GetBytesDownloaded()) to size in block |
|
// Write block size into session info on server |
|
|
|
int it = m_lstDownloaders.Find( pDownloader ); |
|
if ( it == m_lstDownloaders.InvalidIndex() ) |
|
{ |
|
AssertMsg( 0, "Downloader now found in session block downloader list! This should never happen!" ); |
|
return; |
|
} |
|
|
|
CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock ); |
|
const int nSize = pDownloader->GetSize(); |
|
|
|
HTTPStatus_t nStatus = pDownloader->GetStatus(); |
|
|
|
#if _DEBUG |
|
extern ConVar replay_simulatedownloadfailure; |
|
if ( replay_simulatedownloadfailure.GetInt() == 3 ) |
|
{ |
|
nStatus = HTTP_ERROR; |
|
} |
|
#endif |
|
|
|
switch ( nStatus ) |
|
{ |
|
case HTTP_ABORTED: |
|
|
|
pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_ABORTED; |
|
break; |
|
|
|
case HTTP_DONE: |
|
|
|
{ |
|
unsigned char aLocalHash[16]; |
|
|
|
#if _DEBUG |
|
extern ConVar replay_simulate_size_discrepancy; |
|
extern ConVar replay_simulate_bad_hash; |
|
|
|
const bool bSizesDiffer = replay_simulate_size_discrepancy.GetBool() || pBlock->m_uFileSize != pDownloader->GetBytesDownloaded(); |
|
const bool bHashFail = replay_simulate_bad_hash.GetBool() || !pBlock->ValidateData( pData, nSize, aLocalHash ); |
|
#else |
|
const bool bSizesDiffer = pBlock->m_uFileSize != pDownloader->GetBytesDownloaded(); |
|
const bool bHashFail = !pBlock->ValidateData( pData, nSize ); |
|
#endif |
|
|
|
bool bTryAgain = false; |
|
if ( bSizesDiffer ) |
|
{ |
|
AssertMsg( 0, "Number of bytes downloaded differs from size specified in session info file." ); |
|
bTryAgain = true; |
|
} |
|
else if ( bHashFail ) |
|
{ |
|
DBG( "Download failed - either data validation failed\n" ); |
|
|
|
// Data validation failed |
|
pBlock->m_bDataInvalid = true; |
|
bTryAgain = true; |
|
} |
|
else |
|
{ |
|
DBG( "Data validation successful.\n" ); |
|
|
|
// Data validation succeeded |
|
pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED; |
|
|
|
// Clear out any previous errors |
|
pBlock->m_nHttpError = HTTP_ERROR_NONE; |
|
pBlock->m_bDataInvalid = false; |
|
} |
|
|
|
// Failed? |
|
if ( bTryAgain ) |
|
{ |
|
// Attempt to download again if necessary |
|
pBlock->AttemptToResetForDownload(); |
|
|
|
// Report error to OGS. |
|
CL_GetErrorSystem()->OGS_ReportSessionBlockDownloadError( |
|
pDownloader, pBlock, pDownloader->GetBytesDownloaded(), m_nMaxBlock, &bSizesDiffer, |
|
&bHashFail, aLocalHash |
|
); |
|
} |
|
} |
|
|
|
break; |
|
|
|
case HTTP_ERROR: |
|
|
|
// If we've attempted and failed to download the block 3 times, report the error and |
|
// put the block in error state. |
|
if ( pBlock->AttemptToResetForDownload() ) |
|
break; |
|
|
|
// Otherwise, we've max'd out attempts - cache the error state |
|
pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_ERROR; |
|
pBlock->m_nHttpError = pDownloader->GetError(); |
|
|
|
// Now that the block is in the error state, the replay's status will be updated to |
|
// the error state as well (see pSession->UpdateReplayStatuses() below). |
|
|
|
// Report the error to user & OGS |
|
{ |
|
// Create a session block download error. |
|
CL_GetErrorSystem()->OGS_ReportSessionBlockDownloadError( |
|
pDownloader, pBlock, pDownloader->GetBytesDownloaded(), m_nMaxBlock, NULL, NULL, NULL |
|
); |
|
|
|
// Report error to user. |
|
const char *pToken = CHttpDownloader::GetHttpErrorToken( pDownloader->GetError() ); |
|
CL_GetErrorSystem()->AddFormattedErrorFromTokenName( |
|
"#Replay_DL_Err_HTTP_Prefix", |
|
new KeyValues( |
|
"args", |
|
"err", |
|
pToken |
|
) |
|
); |
|
} |
|
|
|
break; |
|
|
|
default: |
|
AssertMsg( 0, "Invalid download state in CSessionBlockDownloader::OnDownloadComplete()" ); |
|
} |
|
|
|
// Flag block for flush |
|
CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false ); |
|
|
|
CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pBlock->m_hSession ) ); Assert( pSession ); |
|
|
|
// Update all replays that care about this block |
|
pSession->UpdateReplayStatuses( pBlock ); |
|
|
|
// Decrement # of downloads |
|
--sm_nNumCurrentDownloads; |
|
} |
|
|
|
//----------------------------------------------------------------------------------------
|
|
|