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.
300 lines
7.6 KiB
300 lines
7.6 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
//=======================================================================================// |
|
|
|
#if defined( WIN32 ) |
|
#include "winlite.h" |
|
#include <WinInet.h> |
|
#endif |
|
|
|
#include "cl_downloader.h" |
|
#include "engine/requestcontext.h" |
|
#include "replaysystem.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
extern IEngineClientReplay *g_pEngineClient; |
|
extern IEngineReplay *g_pEngine; |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
CHttpDownloader::CHttpDownloader( IDownloadHandler *pHandler ) |
|
: m_pHandler( pHandler ), |
|
m_flNextThinkTime( 0.0f ), |
|
m_uBytesDownloaded( 0 ), |
|
m_uSize( 0 ), |
|
m_pThreadState( NULL ), |
|
m_bDone( false ), |
|
m_nHttpError( HTTP_ERROR_NONE ), |
|
m_nHttpStatus( HTTP_INVALID ), |
|
m_pBytesDownloaded( NULL ), |
|
m_pUserData( NULL ) |
|
{ |
|
} |
|
|
|
CHttpDownloader::~CHttpDownloader() |
|
{ |
|
CleanupThreadIfDone(); |
|
} |
|
|
|
bool CHttpDownloader::CleanupThreadIfDone() |
|
{ |
|
if ( !m_pThreadState || !m_pThreadState->threadDone ) |
|
return false; |
|
|
|
// NOTE: The context's "data" member will have already been cleaned up by the |
|
// download thread at this point. |
|
delete m_pThreadState; |
|
m_pThreadState = NULL; |
|
|
|
return true; |
|
} |
|
|
|
bool CHttpDownloader::BeginDownload( const char *pURL, const char *pGamePath, void *pUserData, uint32 *pBytesDownloaded ) |
|
{ |
|
if ( !pURL || !pURL[0] ) |
|
return false; |
|
|
|
m_pThreadState = new RequestContext_t(); |
|
if ( !m_pThreadState ) |
|
return false; |
|
|
|
// Cache any user data |
|
m_pUserData = pUserData; |
|
|
|
// Cache bytes downloaded |
|
m_pBytesDownloaded = pBytesDownloaded; |
|
|
|
// Setup request context |
|
Replay_CrackURL( pURL, m_pThreadState->baseURL, m_pThreadState->urlPath ); |
|
m_pThreadState->bAsHTTP = true; |
|
|
|
if ( pGamePath ) |
|
{ |
|
m_pThreadState->bSuppressFileWrite = false; |
|
V_strcpy( m_pThreadState->gamePath, pGamePath ); |
|
|
|
// Generate the actual filename to save. Well, it's not |
|
// absolute, but this will work. |
|
V_strcpy_safe( m_pThreadState->absLocalPath, g_pEngine->GetGameDir() ); |
|
V_AppendSlash( m_pThreadState->absLocalPath, sizeof(m_pThreadState->absLocalPath) ); |
|
V_strcat_safe( m_pThreadState->absLocalPath, pGamePath ); |
|
} |
|
else |
|
{ |
|
m_pThreadState->bSuppressFileWrite = true; |
|
} |
|
|
|
// Cache URL - for debugging |
|
V_strcpy( m_szURL, pURL ); |
|
|
|
// Spawn the download thread |
|
extern IDownloadSystem *g_pDownloadSystem; |
|
return g_pDownloadSystem->CreateDownloadThread( m_pThreadState ) != 0; |
|
} |
|
|
|
void CHttpDownloader::AbortDownloadAndCleanup() |
|
{ |
|
// Make sure that this function isn't executed simultaneously by |
|
// multiple threads in order to avoid use-after-free crashes during |
|
// shutdown. |
|
AUTO_LOCK( m_lock ); |
|
|
|
if ( !m_pThreadState ) |
|
return; |
|
|
|
// Thread already completed? |
|
if ( m_pThreadState->threadDone ) |
|
{ |
|
CleanupThreadIfDone(); |
|
return; |
|
} |
|
|
|
// Loop until the thread cleans up |
|
m_pThreadState->shouldStop = true; |
|
while ( !m_pThreadState->threadDone ) |
|
; |
|
|
|
// Cache state for handler |
|
m_nHttpError = m_pThreadState->error; |
|
m_nHttpStatus = HTTP_ABORTED; // Force this to be safe |
|
m_uBytesDownloaded = 0; |
|
m_uSize = m_pThreadState->nBytesTotal; |
|
m_bDone = true; |
|
|
|
InvokeHandler(); |
|
CleanupThreadIfDone(); |
|
} |
|
|
|
void CHttpDownloader::Think() |
|
{ |
|
const float flHostTime = g_pEngine->GetHostTime(); |
|
if ( m_flNextThinkTime > flHostTime ) |
|
return; |
|
|
|
if ( !m_pThreadState ) |
|
return; |
|
|
|
// If thread is done, cleanup now |
|
if ( CleanupThreadIfDone() ) |
|
return; |
|
|
|
// If we haven't already set shouldStop, check the download status |
|
if ( !m_pThreadState->shouldStop ) |
|
{ |
|
// Security measure: make sure the file size isn't outrageous |
|
const bool bEvilFileSize = m_pThreadState->nBytesTotal && |
|
m_pThreadState->nBytesTotal >= DOWNLOAD_MAX_SIZE; |
|
#if _DEBUG |
|
extern ConVar replay_simulate_evil_download_size; |
|
if ( replay_simulate_evil_download_size.GetBool() || bEvilFileSize ) |
|
#else |
|
if ( bEvilFileSize ) |
|
#endif |
|
{ |
|
AbortDownloadAndCleanup(); |
|
return; |
|
} |
|
|
|
bool bConnecting = false; // For fall-through in HTTP_CONNECTING case. |
|
|
|
#if _DEBUG |
|
extern ConVar replay_simulatedownloadfailure; |
|
if ( replay_simulatedownloadfailure.GetInt() == 1 ) |
|
{ |
|
m_pThreadState->status = HTTP_ERROR; |
|
} |
|
#endif |
|
|
|
switch ( m_pThreadState->status ) |
|
{ |
|
case HTTP_CONNECTING: |
|
|
|
// Call connecting handler |
|
if ( m_pHandler ) |
|
{ |
|
m_pHandler->OnConnecting( this ); |
|
} |
|
|
|
bConnecting = true; |
|
|
|
// Fall-through |
|
|
|
case HTTP_FETCH: |
|
|
|
m_uBytesDownloaded = (uint32)m_pThreadState->nBytesCurrent; |
|
m_uSize = m_pThreadState->nBytesTotal; |
|
|
|
Assert( m_uBytesDownloaded <= m_uSize ); |
|
|
|
// Call fetch handle |
|
if ( !bConnecting && m_pHandler ) |
|
{ |
|
m_pHandler->OnFetch( this ); |
|
} |
|
|
|
break; |
|
|
|
case HTTP_ABORTED: |
|
case HTTP_DONE: |
|
case HTTP_ERROR: |
|
|
|
// Cache state |
|
m_nHttpError = m_pThreadState->error; |
|
m_nHttpStatus = m_pThreadState->status; |
|
m_uBytesDownloaded = (uint32)m_pThreadState->nBytesCurrent; |
|
m_uSize = m_pThreadState->nBytesTotal; // NOTE: Need to do this here in the case that a file is small enough that we never hit HTTP_FETCH |
|
m_bDone = true; |
|
|
|
// Call handler |
|
InvokeHandler(); |
|
|
|
// Tell the thread to cleanup so we can free it |
|
m_pThreadState->shouldStop = true; |
|
|
|
break; |
|
} |
|
} |
|
|
|
// Write bytes for user if changed |
|
if ( m_pBytesDownloaded && *m_pBytesDownloaded != m_uBytesDownloaded ) |
|
{ |
|
*m_pBytesDownloaded = m_uBytesDownloaded; |
|
IF_REPLAY_DBG( Warning( "%s: Downloaded %i/%i bytes\n", m_szURL, m_uBytesDownloaded, m_uSize ) ); |
|
} |
|
|
|
// Set next think time |
|
m_flNextThinkTime = flHostTime + 0.1f; |
|
} |
|
|
|
void CHttpDownloader::InvokeHandler() |
|
{ |
|
if ( !m_pHandler ) |
|
return; |
|
|
|
// NOTE: Don't delete the downloader in OnDownloadComplete()! |
|
m_pHandler->OnDownloadComplete( this, m_pThreadState->data ); |
|
} |
|
|
|
// This does not increment the "ErrorCounter" field and should only be called from code |
|
// that eventually calls into OGS_ReportGenericError(). |
|
KeyValues *CHttpDownloader::GetOgsRow( int nErrorCounter ) const |
|
{ |
|
KeyValues *pResult = new KeyValues( "TF2ReplayHttpDownloadErrors" ); |
|
pResult->SetInt( "ErrorCounter", nErrorCounter ); |
|
pResult->SetInt( "BytesDownloaded", (int)m_uBytesDownloaded ); |
|
pResult->SetInt( "BytesTotal", (int)m_uSize ); |
|
pResult->SetInt( "HttpStatus", m_nHttpStatus ); |
|
pResult->SetInt( "HttpError", m_nHttpError ); |
|
pResult->SetString( "URL", m_szURL ); |
|
|
|
return pResult; |
|
} |
|
|
|
/*static*/ const char *CHttpDownloader::GetHttpErrorToken( HTTPError_t nError ) |
|
{ |
|
switch ( nError ) |
|
{ |
|
case HTTP_ERROR_ZERO_LENGTH_FILE: return "#HTTPError_ZeroLengthFile"; |
|
case HTTP_ERROR_CONNECTION_CLOSED: return "#HTTPError_ConnectionClosed"; |
|
case HTTP_ERROR_INVALID_URL: return "#HTTPError_InvalidURL"; |
|
case HTTP_ERROR_INVALID_PROTOCOL: return "#HTTPError_InvalidProtocol"; |
|
case HTTP_ERROR_CANT_BIND_SOCKET: return "#HTTPError_CantBindSocket"; |
|
case HTTP_ERROR_CANT_CONNECT: return "#HTTPError_CantConnect"; |
|
case HTTP_ERROR_NO_HEADERS: return "#HTTPError_NoHeaders"; |
|
case HTTP_ERROR_FILE_NONEXISTENT: return "#HTTPError_NonExistent"; |
|
} |
|
|
|
return "#HTTPError_Unknown"; |
|
} |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
#ifdef _DEBUG |
|
|
|
CHttpDownloader *g_pTestDownload = NULL; |
|
ConVar replay_forcedownloadurl( "replay_forcedownloadurl", "" ); |
|
|
|
CON_COMMAND( replay_testdownloader_start, "" ) |
|
{ |
|
const char *pGamePath = Replay_va( "%s%s", CL_GetRecordingSessionBlockManager()->GetSavePath(), "testdownload" ); |
|
|
|
g_pTestDownload = new CHttpDownloader(); |
|
g_pTestDownload->BeginDownload( args[1], pGamePath ); |
|
} |
|
|
|
CON_COMMAND( replay_testdownloader_abort, "" ) |
|
{ |
|
if ( !g_pTestDownload ) |
|
return; |
|
|
|
g_pTestDownload->AbortDownloadAndCleanup(); |
|
|
|
delete g_pTestDownload; |
|
g_pTestDownload = NULL; |
|
} |
|
|
|
#endif |