Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cl_sessionblockdownloader.h"
  5. #include "replay/ienginereplay.h"
  6. #include "cl_recordingsessionblockmanager.h"
  7. #include "cl_replaycontext.h"
  8. #include "cl_recordingsession.h"
  9. #include "cl_recordingsessionblock.h"
  10. #include "errorsystem.h"
  11. #include "convar.h"
  12. #include "vprof.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. //----------------------------------------------------------------------------------------
  16. extern IEngineReplay *g_pEngine;
  17. extern ConVar replay_maxconcurrentdownloads;
  18. //----------------------------------------------------------------------------------------
  19. int CSessionBlockDownloader::sm_nNumCurrentDownloads = 0;
  20. //----------------------------------------------------------------------------------------
  21. CSessionBlockDownloader::CSessionBlockDownloader()
  22. : m_nMaxBlock( -1 )
  23. {
  24. }
  25. void CSessionBlockDownloader::Shutdown()
  26. {
  27. AbortDownloadsAndCleanup( NULL );
  28. Assert( sm_nNumCurrentDownloads == 0 );
  29. }
  30. void CSessionBlockDownloader::AbortDownloadsAndCleanup( CClientRecordingSession *pSession )
  31. {
  32. // NOTE: sm_nNumCurrentDownloads will be decremented in OnDownloadComplete(), which is
  33. // invoked by CHttpDownloader::AbortDownloadAndCleanup()
  34. // Abort any remaining downloads - callbacks will be invoked, so this shutdown
  35. // should be called before any of those objects are cleaned up.
  36. FOR_EACH_LL( m_lstDownloaders, i )
  37. {
  38. CHttpDownloader *pCurDownloader = m_lstDownloaders[ i ];
  39. // If a session was passed in, make sure it has the same handle as the block
  40. CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pCurDownloader->GetUserData();
  41. if ( pSession && ( !pBlock || pBlock->m_hSession != pSession->GetHandle() ) )
  42. continue;
  43. pCurDownloader->AbortDownloadAndCleanup();
  44. delete pCurDownloader;
  45. }
  46. m_lstDownloaders.RemoveAll();
  47. }
  48. bool CSessionBlockDownloader::AtMaxConcurrentDownloads() const
  49. {
  50. return ( sm_nNumCurrentDownloads >= replay_maxconcurrentdownloads.GetInt() );
  51. }
  52. float CSessionBlockDownloader::GetNextThinkTime() const
  53. {
  54. return g_pEngine->GetHostTime() + 0.5f;
  55. }
  56. void CSessionBlockDownloader::Think()
  57. {
  58. VPROF_BUDGET( "CSessionBlockDownloader::Think", VPROF_BUDGETGROUP_REPLAY );
  59. CBaseThinker::Think();
  60. // Hack to not think right away
  61. if ( g_pEngine->GetHostTime() < 3 )
  62. return;
  63. // Don't go over the desired maximum # of concurrent downloads
  64. if ( !AtMaxConcurrentDownloads() )
  65. {
  66. // Go through all blocks and begin downloading any that the server index downloader
  67. // has determined are ready
  68. CClientRecordingSessionBlockManager *pBlockManager = CL_GetRecordingSessionBlockManager();
  69. FOR_EACH_OBJ( pBlockManager, i )
  70. {
  71. CClientRecordingSessionBlock *pBlock = CL_CastBlock( pBlockManager->m_vecObjs[ i ] );
  72. // Checks to see if the remote status is marked as ready for download
  73. if ( !pBlock->ShouldDownloadNow() )
  74. continue;
  75. // Lookup the session for the block
  76. CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->Find( pBlock->m_hSession ) );
  77. if ( !pSession )
  78. {
  79. AssertMsg( 0, "Session for block not found! This should never happen!" );
  80. continue;
  81. }
  82. // Do we need any blocks at all from this session? Is this block within range?
  83. int iLastBlockToDownload = pSession->GetLastBlockToDownload();
  84. if ( iLastBlockToDownload < 0 || pBlock->m_iReconstruction > iLastBlockToDownload )
  85. {
  86. continue;
  87. }
  88. // Begin the download
  89. CHttpDownloader *pDownloader = new CHttpDownloader( this );
  90. const char *pFilename = V_UnqualifiedFileName( pBlock->m_szFullFilename );
  91. #ifdef _DEBUG
  92. extern ConVar replay_forcedownloadurl;
  93. const char *pForceURL = replay_forcedownloadurl.GetString();
  94. const char *pURL = pForceURL[0] ? pForceURL : Replay_va( "%s%s", pSession->m_strBaseDownloadURL.Get(), pFilename );
  95. #else
  96. const char *pURL = Replay_va( "%s%s", pSession->m_strBaseDownloadURL.Get(), pFilename );
  97. #endif
  98. const char *pGamePath = Replay_va( "%s%s", CL_GetRecordingSessionBlockManager()->GetSavePath(), pFilename );
  99. pDownloader->BeginDownload( pURL, pGamePath, (void *)pBlock, &pBlock->m_uBytesDownloaded );
  100. IF_REPLAY_DBG(
  101. Warning ( "%s block %i from %s to path %s...\n",
  102. pBlock->GetNumDownloadAttempts() ? "RETRYING download for" : "Downloading" ,
  103. pBlock->m_iReconstruction, pURL, pGamePath )
  104. );
  105. // Add the downloader
  106. m_lstDownloaders.AddToTail( pDownloader );
  107. // Update block's status
  108. pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADING;
  109. // Mark as dirty
  110. CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false );
  111. // Update # of concurrent downloads
  112. ++sm_nNumCurrentDownloads;
  113. // Get out if we're at max downloads now
  114. if ( AtMaxConcurrentDownloads() )
  115. break;
  116. }
  117. }
  118. int it = m_lstDownloaders.Head();
  119. while ( it != m_lstDownloaders.InvalidIndex() )
  120. {
  121. // Remove finished downloaders
  122. CHttpDownloader *pCurDownloader = m_lstDownloaders[ it ];
  123. if ( pCurDownloader->IsDone() && pCurDownloader->CanDelete() )
  124. {
  125. int itRemove = it;
  126. // Next
  127. it = m_lstDownloaders.Next( it );
  128. // Remove the downloader from the list
  129. m_lstDownloaders.Remove( itRemove );
  130. // Free the downloader
  131. delete pCurDownloader;
  132. }
  133. else
  134. {
  135. // Let the downloader think
  136. pCurDownloader->Think();
  137. // Next
  138. it = m_lstDownloaders.Next( it );
  139. }
  140. }
  141. }
  142. void CSessionBlockDownloader::OnConnecting( CHttpDownloader *pDownloader )
  143. {
  144. CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock );
  145. pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_CONNECTING;
  146. }
  147. void CSessionBlockDownloader::OnFetch( CHttpDownloader *pDownloader )
  148. {
  149. CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock );
  150. pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADING;
  151. }
  152. void CSessionBlockDownloader::OnDownloadComplete( CHttpDownloader *pDownloader, const unsigned char *pData )
  153. {
  154. // TODO: Compare downloaded byte size (pDownloader->GetBytesDownloaded()) to size in block
  155. // Write block size into session info on server
  156. int it = m_lstDownloaders.Find( pDownloader );
  157. if ( it == m_lstDownloaders.InvalidIndex() )
  158. {
  159. AssertMsg( 0, "Downloader now found in session block downloader list! This should never happen!" );
  160. return;
  161. }
  162. CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock );
  163. const int nSize = pDownloader->GetSize();
  164. HTTPStatus_t nStatus = pDownloader->GetStatus();
  165. #if _DEBUG
  166. extern ConVar replay_simulatedownloadfailure;
  167. if ( replay_simulatedownloadfailure.GetInt() == 3 )
  168. {
  169. nStatus = HTTP_ERROR;
  170. }
  171. #endif
  172. switch ( nStatus )
  173. {
  174. case HTTP_ABORTED:
  175. pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_ABORTED;
  176. break;
  177. case HTTP_DONE:
  178. {
  179. unsigned char aLocalHash[16];
  180. #if _DEBUG
  181. extern ConVar replay_simulate_size_discrepancy;
  182. extern ConVar replay_simulate_bad_hash;
  183. const bool bSizesDiffer = replay_simulate_size_discrepancy.GetBool() || pBlock->m_uFileSize != pDownloader->GetBytesDownloaded();
  184. const bool bHashFail = replay_simulate_bad_hash.GetBool() || !pBlock->ValidateData( pData, nSize, aLocalHash );
  185. #else
  186. const bool bSizesDiffer = pBlock->m_uFileSize != pDownloader->GetBytesDownloaded();
  187. const bool bHashFail = !pBlock->ValidateData( pData, nSize );
  188. #endif
  189. bool bTryAgain = false;
  190. if ( bSizesDiffer )
  191. {
  192. AssertMsg( 0, "Number of bytes downloaded differs from size specified in session info file." );
  193. bTryAgain = true;
  194. }
  195. else if ( bHashFail )
  196. {
  197. DBG( "Download failed - either data validation failed\n" );
  198. // Data validation failed
  199. pBlock->m_bDataInvalid = true;
  200. bTryAgain = true;
  201. }
  202. else
  203. {
  204. DBG( "Data validation successful.\n" );
  205. // Data validation succeeded
  206. pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED;
  207. // Clear out any previous errors
  208. pBlock->m_nHttpError = HTTP_ERROR_NONE;
  209. pBlock->m_bDataInvalid = false;
  210. }
  211. // Failed?
  212. if ( bTryAgain )
  213. {
  214. // Attempt to download again if necessary
  215. pBlock->AttemptToResetForDownload();
  216. // Report error to OGS.
  217. CL_GetErrorSystem()->OGS_ReportSessionBlockDownloadError(
  218. pDownloader, pBlock, pDownloader->GetBytesDownloaded(), m_nMaxBlock, &bSizesDiffer,
  219. &bHashFail, aLocalHash
  220. );
  221. }
  222. }
  223. break;
  224. case HTTP_ERROR:
  225. // If we've attempted and failed to download the block 3 times, report the error and
  226. // put the block in error state.
  227. if ( pBlock->AttemptToResetForDownload() )
  228. break;
  229. // Otherwise, we've max'd out attempts - cache the error state
  230. pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_ERROR;
  231. pBlock->m_nHttpError = pDownloader->GetError();
  232. // Now that the block is in the error state, the replay's status will be updated to
  233. // the error state as well (see pSession->UpdateReplayStatuses() below).
  234. // Report the error to user & OGS
  235. {
  236. // Create a session block download error.
  237. CL_GetErrorSystem()->OGS_ReportSessionBlockDownloadError(
  238. pDownloader, pBlock, pDownloader->GetBytesDownloaded(), m_nMaxBlock, NULL, NULL, NULL
  239. );
  240. // Report error to user.
  241. const char *pToken = CHttpDownloader::GetHttpErrorToken( pDownloader->GetError() );
  242. CL_GetErrorSystem()->AddFormattedErrorFromTokenName(
  243. "#Replay_DL_Err_HTTP_Prefix",
  244. new KeyValues(
  245. "args",
  246. "err",
  247. pToken
  248. )
  249. );
  250. }
  251. break;
  252. default:
  253. AssertMsg( 0, "Invalid download state in CSessionBlockDownloader::OnDownloadComplete()" );
  254. }
  255. // Flag block for flush
  256. CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false );
  257. CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pBlock->m_hSession ) ); Assert( pSession );
  258. // Update all replays that care about this block
  259. pSession->UpdateReplayStatuses( pBlock );
  260. // Decrement # of downloads
  261. --sm_nNumCurrentDownloads;
  262. }
  263. //----------------------------------------------------------------------------------------