Counter Strike : Global Offensive Source Code
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.

1005 lines
30 KiB

  1. //========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. //--------------------------------------------------------------------------------------------------------------
  9. // download.cpp
  10. //
  11. // Implementation file for optional HTTP asset downloading
  12. // Author: Matthew D. Campbell ([email protected]), 2004
  13. //--------------------------------------------------------------------------------------------------------------
  14. //--------------------------------------------------------------------------------------------------------------
  15. // Includes
  16. //--------------------------------------------------------------------------------------------------------------
  17. // fopen is needed for the bzip code
  18. #undef PROTECT_FILEIO_FUNCTIONS
  19. #undef fopen
  20. #if defined( WIN32 ) && !defined( _X360 )
  21. #include "winlite.h"
  22. #include <WinInet.h>
  23. #endif
  24. #include <assert.h>
  25. #include "download.h"
  26. #include "tier0/platform.h"
  27. #include "download_internal.h"
  28. #include "client.h"
  29. #include <keyvalues.h>
  30. #include "filesystem.h"
  31. #include "filesystem_engine.h"
  32. #include "server.h"
  33. #include "vgui_baseui_interface.h"
  34. #include "vprof.h"
  35. #include "net_chan.h"
  36. #include "tier1/interface.h"
  37. #include "interfaces/interfaces.h"
  38. #include "vgui/ILocalize.h"
  39. #include "../utils/bzip2/bzlib.h"
  40. #if defined( _X360 )
  41. #include "xbox/xbox_win32stubs.h"
  42. #endif
  43. #if defined( _PS3 )
  44. #include "ps3/ps3_win32stubs.h"
  45. #endif
  46. // memdbgon must be the last include file in a .cpp file!!!
  47. #include "tier0/memdbgon.h"
  48. extern IFileSystem *g_pFileSystem;
  49. static const char *CacheDirectory = "cache";
  50. static const char *CacheFilename = "cache/DownloadCache.db";
  51. Color DownloadColor ( 0, 200, 100, 255 );
  52. Color DownloadErrorColor ( 200, 100, 100, 255 );
  53. Color DownloadCompleteColor ( 100, 200, 100, 255 );
  54. DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_DownloadManager, "DownloadManager" );
  55. //--------------------------------------------------------------------------------------------------------------
  56. static char * CloneString( const char *original )
  57. {
  58. char *newString = new char[ Q_strlen( original ) + 1 ];
  59. Q_strcpy( newString, original );
  60. return newString;
  61. }
  62. //--------------------------------------------------------------------------------------------------------------
  63. // Class Definitions
  64. //--------------------------------------------------------------------------------------------------------------
  65. //--------------------------------------------------------------------------------------------------------------
  66. // Purpose: Implements download cache manager
  67. //--------------------------------------------------------------------------------------------------------------
  68. class DownloadCache
  69. {
  70. public:
  71. DownloadCache();
  72. ~DownloadCache();
  73. void Init();
  74. void GetCachedData( RequestContext *rc ); ///< Loads cached data, if any
  75. void PersistToDisk( const RequestContext *rc ); ///< Writes out a completed download to disk
  76. void PersistToCache( const RequestContext *rc ); ///< Writes out a partial download (lost connection, user abort, etc) to cache
  77. private:
  78. KeyValues *m_cache;
  79. void GetCacheFilename( const RequestContext *rc, char cachePath[_MAX_PATH] );
  80. void GenerateCacheFilename( const RequestContext *rc, char cachePath[_MAX_PATH] );
  81. void BuildKeyNames( const char *gamePath ); ///< Convenience function to build the keys to index into m_cache
  82. char m_cachefileKey[BufferSize + 64];
  83. char m_timestampKey[BufferSize + 64];
  84. };
  85. static DownloadCache *TheDownloadCache = NULL;
  86. //--------------------------------------------------------------------------------------------------------------
  87. DownloadCache::DownloadCache()
  88. {
  89. m_cache = NULL;
  90. }
  91. //--------------------------------------------------------------------------------------------------------------
  92. DownloadCache::~DownloadCache()
  93. {
  94. }
  95. //--------------------------------------------------------------------------------------------------------------
  96. void DownloadCache::BuildKeyNames( const char *gamePath )
  97. {
  98. if ( !gamePath )
  99. {
  100. m_cachefileKey[0] = 0;
  101. m_timestampKey[0] = 0;
  102. return;
  103. }
  104. char *tmpGamePath = CloneString( gamePath );
  105. char *tmp = tmpGamePath;
  106. while ( *tmp )
  107. {
  108. if ( *tmp == '/' || *tmp == '\\' )
  109. {
  110. *tmp = '_';
  111. }
  112. ++tmp;
  113. }
  114. Q_snprintf( m_cachefileKey, sizeof( m_cachefileKey ), "cachefile_%s", tmpGamePath );
  115. Q_snprintf( m_timestampKey, sizeof( m_timestampKey ), "timestamp_%s", tmpGamePath );
  116. delete[] tmpGamePath;
  117. }
  118. //--------------------------------------------------------------------------------------------------------------
  119. void DownloadCache::Init()
  120. {
  121. if ( m_cache )
  122. {
  123. m_cache->deleteThis();
  124. }
  125. m_cache = new KeyValues( "DownloadCache" );
  126. m_cache->LoadFromFile( g_pFileSystem, CacheFilename, NULL );
  127. g_pFileSystem->CreateDirHierarchy( CacheDirectory, "DEFAULT_WRITE_PATH" );
  128. }
  129. //--------------------------------------------------------------------------------------------------------------
  130. void DownloadCache::GetCachedData( RequestContext *rc )
  131. {
  132. if ( !m_cache )
  133. return;
  134. char cachePath[_MAX_PATH];
  135. GetCacheFilename( rc, cachePath );
  136. if ( !(*cachePath) )
  137. return;
  138. FileHandle_t fp = g_pFileSystem->Open( cachePath, "rb" );
  139. if ( fp == FILESYSTEM_INVALID_HANDLE )
  140. return;
  141. int size = g_pFileSystem->Size(fp);
  142. rc->cacheData = new unsigned char[size];
  143. int status = g_pFileSystem->Read( rc->cacheData, size, fp );
  144. g_pFileSystem->Close( fp );
  145. if ( !status )
  146. {
  147. delete[] rc->cacheData;
  148. rc->cacheData = NULL;
  149. }
  150. else
  151. {
  152. BuildKeyNames( rc->gamePath );
  153. rc->nBytesCached = size;
  154. strncpy( rc->cachedTimestamp, m_cache->GetString( m_timestampKey, "" ), BufferSize );
  155. }
  156. }
  157. //--------------------------------------------------------------------------------------------------------------
  158. /**
  159. * Takes a data stream compressed with bzip2, and writes it out to disk, uncompresses it, and deletes the
  160. * compressed version.
  161. */
  162. static bool DecompressBZipToDisk( const char *outFilename, const char *srcFilename, char *data, int bytesTotal )
  163. {
  164. if ( g_pFileSystem->FileExists( outFilename ) || !data || bytesTotal < 1 )
  165. {
  166. return false;
  167. }
  168. // Create the subdirs
  169. char * tmpDir = CloneString( outFilename );
  170. COM_CreatePath( tmpDir );
  171. delete[] tmpDir;
  172. // open the file for writing
  173. char fullSrcPath[MAX_PATH];
  174. Q_MakeAbsolutePath( fullSrcPath, sizeof( fullSrcPath ), srcFilename, com_gamedir );
  175. if ( !g_pFileSystem->FileExists( fullSrcPath ) )
  176. {
  177. // Write out the .bz2 file, for simplest decompression
  178. FileHandle_t ifp = g_pFileSystem->Open( fullSrcPath, "wb" );
  179. if ( !ifp )
  180. {
  181. return false;
  182. }
  183. int bytesWritten = g_pFileSystem->Write( data, bytesTotal, ifp );
  184. g_pFileSystem->Close( ifp );
  185. if ( bytesWritten != bytesTotal )
  186. {
  187. // couldn't write out all of the .bz2 file
  188. g_pFileSystem->RemoveFile( srcFilename );
  189. return false;
  190. }
  191. }
  192. // Prepare the uncompressed filehandle
  193. FileHandle_t ofp = g_pFileSystem->Open( outFilename, "wb" );
  194. if ( !ofp )
  195. {
  196. g_pFileSystem->RemoveFile( srcFilename );
  197. return false;
  198. }
  199. // And decompress!
  200. const int OutBufSize = 65536;
  201. char buf[ OutBufSize ];
  202. BZFILE *bzfp = BZ2_bzopen( fullSrcPath, "rb" );
  203. int totalBytes = 0;
  204. while ( 1 )
  205. {
  206. int bytesRead = BZ2_bzread( bzfp, buf, OutBufSize );
  207. if ( bytesRead < 0 )
  208. {
  209. break; // error out
  210. }
  211. if ( bytesRead > 0 )
  212. {
  213. int bytesWritten = g_pFileSystem->Write( buf, bytesRead, ofp );
  214. if ( bytesWritten != bytesRead )
  215. {
  216. break; // error out
  217. }
  218. else
  219. {
  220. totalBytes += bytesWritten;
  221. static const int s_numMaxFileSizeBytes = CommandLine()->ParmValue( "-maxdownloadfilesizemb", 150 )*1024*1024;
  222. if ( totalBytes > s_numMaxFileSizeBytes )
  223. {
  224. Warning( "DecompressBZipToDisk: '%s' too big (max %.1f megabytes, use launch option -maxdownloadfilesizemb N to override).\n", srcFilename, float( s_numMaxFileSizeBytes )/float( 1024*1024 ) );
  225. break; // error out
  226. }
  227. }
  228. }
  229. else
  230. {
  231. g_pFileSystem->Close( ofp );
  232. BZ2_bzclose( bzfp );
  233. g_pFileSystem->RemoveFile( srcFilename );
  234. return true;
  235. }
  236. }
  237. // We failed somewhere, so clean up and exit
  238. g_pFileSystem->Close( ofp );
  239. BZ2_bzclose( bzfp );
  240. g_pFileSystem->RemoveFile( srcFilename );
  241. g_pFileSystem->RemoveFile( outFilename );
  242. return false;
  243. }
  244. //--------------------------------------------------------------------------------------------------------------
  245. void DownloadCache::PersistToDisk( const RequestContext *rc )
  246. {
  247. if ( !m_cache )
  248. return;
  249. if ( rc && rc->data && rc->nBytesTotal )
  250. {
  251. char gamePath[MAX_PATH];
  252. if ( rc->bIsBZ2 )
  253. {
  254. Q_StripExtension( rc->gamePath, gamePath, sizeof( gamePath ) );
  255. }
  256. else
  257. {
  258. Q_strncpy( gamePath, rc->gamePath, sizeof( gamePath ) );
  259. }
  260. if ( !g_pFileSystem->FileExists( gamePath ) )
  261. {
  262. // Create the subdirs
  263. char * tmpDir = CloneString( gamePath );
  264. COM_CreatePath( tmpDir );
  265. delete[] tmpDir;
  266. bool success = false;
  267. if ( rc->bIsBZ2 )
  268. {
  269. success = DecompressBZipToDisk( gamePath, rc->gamePath, reinterpret_cast< char * >(rc->data), rc->nBytesTotal );
  270. }
  271. else
  272. {
  273. FileHandle_t fp = g_pFileSystem->Open( gamePath, "wb" );
  274. if ( fp )
  275. {
  276. g_pFileSystem->Write( rc->data, rc->nBytesTotal, fp );
  277. g_pFileSystem->Close( fp );
  278. success = true;
  279. }
  280. }
  281. if ( success )
  282. {
  283. // write succeeded. remove any old data from the cache.
  284. char cachePath[_MAX_PATH];
  285. GetCacheFilename( rc, cachePath );
  286. if ( cachePath[0] )
  287. {
  288. g_pFileSystem->RemoveFile( cachePath, NULL );
  289. }
  290. BuildKeyNames( rc->gamePath );
  291. KeyValues *kv = m_cache->FindKey( m_cachefileKey, false );
  292. if ( kv )
  293. {
  294. m_cache->RemoveSubKey( kv );
  295. }
  296. kv = m_cache->FindKey( m_timestampKey, false );
  297. if ( kv )
  298. {
  299. m_cache->RemoveSubKey( kv );
  300. }
  301. }
  302. }
  303. }
  304. m_cache->SaveToFile( g_pFileSystem, CacheFilename, NULL );
  305. }
  306. //--------------------------------------------------------------------------------------------------------------
  307. void DownloadCache::PersistToCache( const RequestContext *rc )
  308. {
  309. if ( !m_cache || !rc || !rc->data || !rc->nBytesTotal || !rc->nBytesCurrent )
  310. return;
  311. char cachePath[_MAX_PATH];
  312. GenerateCacheFilename( rc, cachePath );
  313. FileHandle_t fp = g_pFileSystem->Open( cachePath, "wb" );
  314. if ( fp )
  315. {
  316. g_pFileSystem->Write( rc->data, rc->nBytesCurrent, fp );
  317. g_pFileSystem->Close( fp );
  318. m_cache->SaveToFile( g_pFileSystem, CacheFilename, NULL );
  319. }
  320. }
  321. //--------------------------------------------------------------------------------------------------------------
  322. void DownloadCache::GetCacheFilename( const RequestContext *rc, char cachePath[_MAX_PATH] )
  323. {
  324. BuildKeyNames( rc->gamePath );
  325. const char *path = m_cache->GetString( m_cachefileKey, NULL );
  326. if ( !path || !StringHasPrefixCaseSensitive( path, CacheDirectory ) )
  327. {
  328. cachePath[0] = 0;
  329. return;
  330. }
  331. strncpy( cachePath, path, _MAX_PATH );
  332. cachePath[_MAX_PATH-1] = 0;
  333. }
  334. //--------------------------------------------------------------------------------------------------------------
  335. void DownloadCache::GenerateCacheFilename( const RequestContext *rc, char cachePath[_MAX_PATH] )
  336. {
  337. GetCacheFilename( rc, cachePath );
  338. BuildKeyNames( rc->gamePath );
  339. m_cache->SetString( m_timestampKey, rc->cachedTimestamp );
  340. if ( !*cachePath )
  341. {
  342. const char * lastSlash = strrchr( rc->gamePath, '/' );
  343. const char * lastBackslash = strrchr( rc->gamePath, '\\' );
  344. const char *gameFilename = rc->gamePath;
  345. if ( lastSlash || lastBackslash )
  346. {
  347. gameFilename = MAX( lastSlash, lastBackslash ) + 1;
  348. }
  349. for( int i=0; i<1000; ++i )
  350. {
  351. Q_snprintf( cachePath, _MAX_PATH, "%s/%s%4.4d", CacheDirectory, gameFilename, i );
  352. if ( !g_pFileSystem->FileExists( cachePath ) )
  353. {
  354. m_cache->SetString( m_cachefileKey, cachePath );
  355. return;
  356. }
  357. }
  358. // all 1000 were invalid?!?
  359. Q_snprintf( cachePath, _MAX_PATH, "%s/overflow", CacheDirectory );
  360. m_cache->SetString( m_cachefileKey, cachePath );
  361. }
  362. }
  363. //--------------------------------------------------------------------------------------------------------------
  364. // Purpose: Implements download manager class
  365. //--------------------------------------------------------------------------------------------------------------
  366. class DownloadManager
  367. {
  368. public:
  369. DownloadManager();
  370. ~DownloadManager();
  371. void Queue( const char *baseURL, const char *gamePath );
  372. void Stop() { Reset(); }
  373. int GetQueueSize() { return m_queuedRequests.Count(); }
  374. bool Update(); ///< Monitors download thread, starts new downloads, and updates progress bar
  375. bool FileReceived( const char *filename, unsigned int requestID, bool isReplayDemoFile );
  376. bool FileDenied( const char *filename, unsigned int requestID, bool isReplayDemoFile );
  377. bool HasMapBeenDownloadedFromServer( const char *serverMapName );
  378. void MarkMapAsDownloadedFromServer( const char *serverMapName );
  379. private:
  380. void Reset(); ///< Cancels any active download, as well as any queued ones
  381. void PruneCompletedRequests(); ///< Check download requests that have been completed to see if their threads have exited
  382. void CheckActiveDownload(); ///< Checks download status, and updates progress bar
  383. void StartNewDownload(); ///< Starts a new download if there are queued requests
  384. void UpdateProgressBar();
  385. typedef CUtlVector< RequestContext * > RequestVector;
  386. RequestVector m_queuedRequests; ///< these are requests waiting to be spawned
  387. RequestContext *m_activeRequest; ///< this is the active request being downloaded in another thread
  388. RequestVector m_completedRequests; ///< these are waiting for the thread to exit
  389. int m_lastPercent; ///< last percent value the progress bar was updated with (to avoid spamming it)
  390. int m_totalRequests; ///< Total number of requests (used to set the top progress bar)
  391. int m_RequestIDCounter; ///< global increasing request ID counter
  392. typedef CUtlVector< char * > StrVector;
  393. StrVector m_downloadedMaps; ///< List of maps for which we have already tried to download assets.
  394. };
  395. //--------------------------------------------------------------------------------------------------------------
  396. static DownloadManager TheDownloadManager;
  397. //--------------------------------------------------------------------------------------------------------------
  398. DownloadManager::DownloadManager()
  399. {
  400. m_activeRequest = NULL;
  401. m_lastPercent = 0;
  402. m_totalRequests = 0;
  403. }
  404. //--------------------------------------------------------------------------------------------------------------
  405. DownloadManager::~DownloadManager()
  406. {
  407. Reset();
  408. for ( int i=0; i<m_downloadedMaps.Count(); ++i )
  409. {
  410. delete[] m_downloadedMaps[i];
  411. }
  412. m_downloadedMaps.RemoveAll();
  413. }
  414. //--------------------------------------------------------------------------------------------------------------
  415. bool DownloadManager::HasMapBeenDownloadedFromServer( const char *serverMapName )
  416. {
  417. if ( !serverMapName )
  418. return false;
  419. for ( int i=0; i<m_downloadedMaps.Count(); ++i )
  420. {
  421. const char *oldServerMapName = m_downloadedMaps[i];
  422. if ( oldServerMapName && !stricmp( serverMapName, oldServerMapName ) )
  423. {
  424. return true;
  425. }
  426. }
  427. return false;
  428. }
  429. bool DownloadManager::FileDenied( const char *filename, unsigned int requestID, bool isReplayDemoFile )
  430. {
  431. if ( !m_activeRequest )
  432. return false;
  433. if ( m_activeRequest->nRequestID != requestID )
  434. return false;
  435. if ( m_activeRequest->bAsHTTP )
  436. return false;
  437. Log_Msg( LOG_DownloadManager, DownloadErrorColor, "Error downloading %s\n", m_activeRequest->gamePath );
  438. UpdateProgressBar();
  439. // try to download the next file
  440. m_completedRequests.AddToTail( m_activeRequest );
  441. m_activeRequest = NULL;
  442. return true;
  443. }
  444. // INFESTED_DLL
  445. #if !defined( DEDICATED )
  446. extern bool g_bASW_Waiting_For_Map_Build;
  447. #endif
  448. bool DownloadManager::FileReceived( const char *filename, unsigned int requestID, bool isReplayDemoFile )
  449. {
  450. if ( !m_activeRequest )
  451. return false;
  452. if ( m_activeRequest->nRequestID != requestID )
  453. return false;
  454. if ( m_activeRequest->bAsHTTP )
  455. return false;
  456. Log_Msg( LOG_DownloadManager, DownloadCompleteColor, "Download finished!\n" );
  457. UpdateProgressBar();
  458. m_completedRequests.AddToTail( m_activeRequest );
  459. m_activeRequest = NULL;
  460. // INFESTED_DLL
  461. static char gamedir[MAX_OSPATH];
  462. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  463. if ( !Q_stricmp( gamedir, "infested" ) )
  464. {
  465. // see if we just recieved a map layout
  466. const char *pExt = V_GetFileExtension( filename );
  467. if ( !Q_stricmp( pExt, "layout" ) )
  468. {
  469. #if !defined( DEDICATED )
  470. // start compiling the map
  471. g_bASW_Waiting_For_Map_Build = true;
  472. #endif
  473. char cmd[ 256 ];
  474. Q_snprintf( cmd, sizeof( cmd ), "asw_build_map %s connecting\n", filename );
  475. Cbuf_AddText( Cbuf_GetCurrentPlayer(), cmd );
  476. }
  477. }
  478. return true;
  479. }
  480. //--------------------------------------------------------------------------------------------------------------
  481. void DownloadManager::MarkMapAsDownloadedFromServer( const char *serverMapName )
  482. {
  483. if ( !serverMapName )
  484. return;
  485. if ( HasMapBeenDownloadedFromServer( serverMapName ) )
  486. return;
  487. m_downloadedMaps.AddToTail( CloneString( serverMapName ) );
  488. return;
  489. }
  490. //--------------------------------------------------------------------------------------------------------------
  491. void DownloadManager::Queue( const char *baseURL, const char *gamePath )
  492. {
  493. if ( !CNetChan::IsValidFileForTransfer( gamePath ) )
  494. return;
  495. bool bAsHTTP = false;
  496. if ( !gamePath )
  497. {
  498. return;
  499. }
  500. VPROF_BUDGET( "DownloadManager::Queue", VPROF_BUDGETGROUP_STEAM );
  501. if ( sv.IsActive() )
  502. {
  503. return; // don't try to download things for the local server (in case a map is missing sounds etc that
  504. // aren't needed to play.
  505. }
  506. // only http downloads
  507. if ( baseURL && ( StringHasPrefix( baseURL, "http://" ) || StringHasPrefix( baseURL, "https://" ) ) )
  508. {
  509. bAsHTTP = true;
  510. }
  511. if ( g_pFileSystem->FileExists( gamePath ) && !CL_ShouldRedownloadFile( gamePath ) )
  512. {
  513. return; // don't download existing files
  514. }
  515. if ( bAsHTTP && !g_pFileSystem->FileExists( va( "%s.bz2", gamePath ) ) )
  516. {
  517. // Queue up an HTTP download of the bzipped asset, in case it exists.
  518. // When a bzipped download finishes, we'll uncompress the file to it's
  519. // original destination, and the queued download of the uncompressed
  520. // file will abort.
  521. ++m_totalRequests;
  522. if ( !TheDownloadCache )
  523. {
  524. TheDownloadCache = new DownloadCache;
  525. TheDownloadCache->Init();
  526. }
  527. RequestContext *rc = new RequestContext;
  528. m_queuedRequests.AddToTail( rc );
  529. memset( rc, 0, sizeof(RequestContext) );
  530. rc->status = HTTP_CONNECTING;
  531. Q_strncpy( rc->basePath, com_gamedir, BufferSize );
  532. Q_strncpy( rc->gamePath, gamePath, BufferSize );
  533. Q_strncat( rc->gamePath, ".bz2", BufferSize, COPY_ALL_CHARACTERS );
  534. Q_FixSlashes( rc->gamePath, '/' ); // only matters for debug prints, which are full URLS, so we want forward slashes
  535. #ifndef DEDICATED
  536. Q_strncpy( rc->serverURL, GetBaseLocalClient().m_NetChannel->GetAddress(), BufferSize );
  537. #endif
  538. rc->bIsBZ2 = true;
  539. rc->bAsHTTP = true;
  540. Q_strncpy( rc->baseURL, baseURL, BufferSize );
  541. Q_strncat( rc->baseURL, "/", BufferSize, COPY_ALL_CHARACTERS );
  542. }
  543. ++m_totalRequests;
  544. if ( !TheDownloadCache )
  545. {
  546. TheDownloadCache = new DownloadCache;
  547. TheDownloadCache->Init();
  548. }
  549. RequestContext *rc = new RequestContext;
  550. m_queuedRequests.AddToTail( rc );
  551. memset( rc, 0, sizeof(RequestContext) );
  552. rc->status = HTTP_CONNECTING;
  553. Q_strncpy( rc->basePath, com_gamedir, BufferSize );
  554. Q_strncpy( rc->gamePath, gamePath, BufferSize );
  555. Q_FixSlashes( rc->gamePath, '/' ); // only matters for debug prints, which are full URLS, so we want forward slashes
  556. #ifndef DEDICATED
  557. Q_strncpy( rc->serverURL, GetBaseLocalClient().m_NetChannel->GetAddress(), BufferSize );
  558. #endif
  559. if ( bAsHTTP )
  560. {
  561. rc->bAsHTTP = true;
  562. Q_strncpy( rc->baseURL, baseURL, BufferSize );
  563. Q_strncat( rc->baseURL, "/", BufferSize, COPY_ALL_CHARACTERS );
  564. }
  565. else
  566. {
  567. rc->bAsHTTP = false;
  568. }
  569. }
  570. //--------------------------------------------------------------------------------------------------------------
  571. void DownloadManager::Reset()
  572. {
  573. // ask the active request to bail
  574. if ( m_activeRequest )
  575. {
  576. Log_Msg( LOG_DownloadManager, DownloadColor, "Aborting download of %s\n", m_activeRequest->gamePath );
  577. if ( m_activeRequest->nBytesTotal && m_activeRequest->nBytesCurrent )
  578. {
  579. // Persist partial data to cache
  580. TheDownloadCache->PersistToCache( m_activeRequest );
  581. }
  582. m_activeRequest->shouldStop = true;
  583. m_completedRequests.AddToTail( m_activeRequest );
  584. m_activeRequest = NULL;
  585. //TODO: StopLoadingProgressBar();
  586. }
  587. // clear out any queued requests
  588. for ( int i=0; i<m_queuedRequests.Count(); ++i )
  589. {
  590. Log_Msg( LOG_DownloadManager, DownloadColor, "Discarding queued download of %s\n", m_queuedRequests[i]->gamePath );
  591. delete m_queuedRequests[i];
  592. }
  593. m_queuedRequests.RemoveAll();
  594. if ( TheDownloadCache )
  595. {
  596. delete TheDownloadCache;
  597. TheDownloadCache = NULL;
  598. }
  599. m_lastPercent = 0;
  600. m_totalRequests = 0;
  601. }
  602. //--------------------------------------------------------------------------------------------------------------
  603. // Check download requests that have been completed to see if their threads have exited
  604. void DownloadManager::PruneCompletedRequests()
  605. {
  606. for ( int i=m_completedRequests.Count()-1; i>=0; --i )
  607. {
  608. if ( m_completedRequests[i]->threadDone || !m_completedRequests[i]->bAsHTTP )
  609. {
  610. if ( m_completedRequests[i]->cacheData )
  611. {
  612. delete[] m_completedRequests[i]->cacheData;
  613. }
  614. delete m_completedRequests[i];
  615. m_completedRequests.Remove( i );
  616. }
  617. }
  618. }
  619. //--------------------------------------------------------------------------------------------------------------
  620. // Checks download status, and updates progress bar
  621. void DownloadManager::CheckActiveDownload()
  622. {
  623. if ( !m_activeRequest )
  624. return;
  625. if ( !m_activeRequest->bAsHTTP )
  626. {
  627. UpdateProgressBar();
  628. return;
  629. }
  630. // check active request for completion / error / progress update
  631. switch ( m_activeRequest->status )
  632. {
  633. case HTTP_DONE:
  634. Log_Msg( LOG_DownloadManager, DownloadCompleteColor, "Download finished!\n" );
  635. UpdateProgressBar();
  636. if ( m_activeRequest->nBytesTotal )
  637. {
  638. // // change it to be updating steam resources
  639. #ifndef DEDICATED
  640. EngineVGui()->UpdateSecondaryProgressBarWithFile( 1, m_activeRequest->gamePath, m_activeRequest->nBytesTotal );
  641. #endif
  642. // Persist complete data to disk, and remove cache entry
  643. TheDownloadCache->PersistToDisk( m_activeRequest );
  644. m_activeRequest->shouldStop = true;
  645. m_completedRequests.AddToTail( m_activeRequest );
  646. m_activeRequest = NULL;
  647. if ( !m_queuedRequests.Count() )
  648. {
  649. //TODO: StopLoadingProgressBar();
  650. //TODO: Cbuf_AddText("retry\n");
  651. }
  652. }
  653. break;
  654. case HTTP_ERROR:
  655. Log_Msg( LOG_DownloadManager, DownloadErrorColor, "Error downloading %s%s\n", m_activeRequest->baseURL, m_activeRequest->gamePath );
  656. UpdateProgressBar();
  657. // try to download the next file
  658. m_activeRequest->shouldStop = true;
  659. m_completedRequests.AddToTail( m_activeRequest );
  660. m_activeRequest = NULL;
  661. if ( !m_queuedRequests.Count() )
  662. {
  663. //TODO: StopLoadingProgressBar();
  664. //TODO: Cbuf_AddText("retry\n");
  665. }
  666. break;
  667. case HTTP_FETCH:
  668. UpdateProgressBar();
  669. // Update progress bar
  670. if ( m_activeRequest->nBytesTotal )
  671. {
  672. int percent = ( m_activeRequest->nBytesCurrent * 100 / m_activeRequest->nBytesTotal );
  673. if ( percent != m_lastPercent )
  674. {
  675. m_lastPercent = percent;
  676. #ifndef DEDICATED
  677. EngineVGui()->UpdateSecondaryProgressBarWithFile( m_lastPercent * 0.01f, m_activeRequest->gamePath, m_activeRequest->nBytesTotal );
  678. #endif
  679. }
  680. }
  681. break;
  682. }
  683. }
  684. //--------------------------------------------------------------------------------------------------------------
  685. // Starts a new download if there are queued requests
  686. void DownloadManager::StartNewDownload()
  687. {
  688. if ( m_activeRequest || !m_queuedRequests.Count() )
  689. return;
  690. while ( !m_activeRequest && m_queuedRequests.Count() )
  691. {
  692. // Remove one request from the queue and make it active
  693. m_activeRequest = m_queuedRequests[0];
  694. m_queuedRequests.Remove( 0 );
  695. if ( g_pFileSystem->FileExists( m_activeRequest->gamePath ) && !CL_ShouldRedownloadFile( m_activeRequest->gamePath ) )
  696. {
  697. Log_Msg( LOG_DownloadManager, DownloadColor, "Skipping existing file %s%s.\n", m_activeRequest->baseURL, m_activeRequest->gamePath );
  698. m_activeRequest->shouldStop = true;
  699. m_activeRequest->threadDone = true;
  700. m_completedRequests.AddToTail( m_activeRequest );
  701. m_activeRequest = NULL;
  702. }
  703. }
  704. if ( !m_activeRequest )
  705. return;
  706. if ( g_pFileSystem->FileExists( m_activeRequest->gamePath ) && !CL_ShouldRedownloadFile( m_activeRequest->gamePath ) )
  707. {
  708. m_activeRequest->shouldStop = true;
  709. m_activeRequest->threadDone = true;
  710. m_completedRequests.AddToTail( m_activeRequest );
  711. m_activeRequest = NULL;
  712. return; // don't download existing files
  713. }
  714. if ( m_activeRequest->bAsHTTP )
  715. {
  716. // Check cache for partial match
  717. TheDownloadCache->GetCachedData( m_activeRequest );
  718. //TODO: ContinueLoadingProgressBar( "Http", m_totalRequests - m_queuedRequests.Count(), 0.0f );
  719. //TODO: SetLoadingProgressBarStatusText( "#GameUI_VerifyingAndDownloading" );
  720. #ifndef DEDICATED
  721. EngineVGui()->UpdateSecondaryProgressBarWithFile( 0, m_activeRequest->gamePath, m_activeRequest->nBytesTotal );
  722. #endif
  723. UpdateProgressBar();
  724. Log_Msg( LOG_DownloadManager, DownloadColor, "Downloading %s%s.\n", m_activeRequest->baseURL, m_activeRequest->gamePath );
  725. m_lastPercent = 0;
  726. // Start the thread
  727. ThreadHandle_t hSimpleThread = CreateSimpleThread( DownloadThread, m_activeRequest );
  728. ReleaseThreadHandle( hSimpleThread );
  729. }
  730. else
  731. {
  732. UpdateProgressBar();
  733. Log_Msg( LOG_DownloadManager, DownloadColor, "Downloading %s.\n", m_activeRequest->gamePath );
  734. m_lastPercent = 0;
  735. #ifndef DEDICATED
  736. m_activeRequest->nRequestID = GetBaseLocalClient().m_NetChannel->RequestFile( m_activeRequest->gamePath, false );
  737. #endif
  738. }
  739. }
  740. //--------------------------------------------------------------------------------------------------------------
  741. void DownloadManager::UpdateProgressBar()
  742. {
  743. if ( !m_activeRequest )
  744. {
  745. return;
  746. }
  747. //wchar_t filenameBuf[MAX_OSPATH];
  748. float progress = 0.0f;
  749. #ifndef DEDICATED
  750. int received, total = 0;
  751. #endif
  752. if ( m_activeRequest->bAsHTTP )
  753. {
  754. int overallPercent = (m_totalRequests - m_queuedRequests.Count() - 1) * 100 / m_totalRequests;
  755. int filePercent = 0;
  756. if ( m_activeRequest->nBytesTotal > 0 )
  757. {
  758. filePercent = ( m_activeRequest->nBytesCurrent * 100 / m_activeRequest->nBytesTotal );
  759. }
  760. progress = (overallPercent + filePercent * 1.0f / m_totalRequests) * 0.01f;
  761. }
  762. else
  763. {
  764. #ifndef DEDICATED
  765. GetBaseLocalClient().m_NetChannel->GetStreamProgress( FLOW_INCOMING, &received, &total );
  766. progress = (float)(received)/(float)(total);
  767. #endif
  768. }
  769. #ifndef DEDICATED
  770. EngineVGui()->UpdateSecondaryProgressBarWithFile( progress, m_activeRequest->gamePath, total );
  771. #endif
  772. }
  773. //--------------------------------------------------------------------------------------------------------------
  774. // Monitors download thread, starts new downloads, and updates progress bar
  775. bool DownloadManager::Update()
  776. {
  777. PruneCompletedRequests();
  778. CheckActiveDownload();
  779. StartNewDownload();
  780. return m_activeRequest != NULL;
  781. }
  782. //--------------------------------------------------------------------------------------------------------------
  783. // Externally-visible function definitions
  784. //--------------------------------------------------------------------------------------------------------------
  785. //--------------------------------------------------------------------------------------------------------------
  786. bool CL_DownloadUpdate(void)
  787. {
  788. return TheDownloadManager.Update();
  789. }
  790. //--------------------------------------------------------------------------------------------------------------
  791. void CL_HTTPStop_f(void)
  792. {
  793. TheDownloadManager.Stop();
  794. }
  795. bool CL_FileReceived( const char *filename, unsigned int requestID, bool isReplayDemoFile )
  796. {
  797. return TheDownloadManager.FileReceived( filename, requestID, isReplayDemoFile );
  798. }
  799. bool CL_FileDenied( const char *filename, unsigned int requestID, bool isReplayDemoFile )
  800. {
  801. return TheDownloadManager.FileDenied( filename, requestID, isReplayDemoFile );
  802. }
  803. //--------------------------------------------------------------------------------------------------------------
  804. extern ConVar sv_downloadurl;
  805. void CL_QueueDownload( const char *filename )
  806. {
  807. TheDownloadManager.Queue( sv_downloadurl.GetString(), filename );
  808. }
  809. //--------------------------------------------------------------------------------------------------------------
  810. int CL_GetDownloadQueueSize(void)
  811. {
  812. return TheDownloadManager.GetQueueSize();
  813. }
  814. //--------------------------------------------------------------------------------------------------------------
  815. int CL_CanUseHTTPDownload(void)
  816. {
  817. if ( sv_downloadurl.GetString()[0] )
  818. {
  819. #ifndef DEDICATED
  820. const char *serverMapName = va( "%s:%s", sv_downloadurl.GetString(), GetBaseLocalClient().m_szLevelName );
  821. return !TheDownloadManager.HasMapBeenDownloadedFromServer( serverMapName );
  822. #endif
  823. }
  824. return 0;
  825. }
  826. //--------------------------------------------------------------------------------------------------------------
  827. void CL_MarkMapAsUsingHTTPDownload(void)
  828. {
  829. #ifndef DEDICATED
  830. const char *serverMapName = va( "%s:%s", sv_downloadurl.GetString(), GetBaseLocalClient().m_szLevelName );
  831. TheDownloadManager.MarkMapAsDownloadedFromServer( serverMapName );
  832. #endif
  833. }
  834. //--------------------------------------------------------------------------------------------------------------
  835. bool CL_ShouldRedownloadFile( const char *filename )
  836. {
  837. // INFESTED_DLL - allow redownloading of the .layout or .bsp files, as these may have changed since we last downloaded them
  838. static char gamedir[MAX_OSPATH];
  839. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  840. if ( !Q_stricmp( gamedir, "infested" ) && ( StringHasPrefix( filename + 5, "gridrandom" ) || StringHasPrefix( filename + 5, "output" ) ) )
  841. {
  842. char extension[12];
  843. Q_ExtractFileExtension( filename, extension, sizeof( extension ) );
  844. if ( !Q_stricmp( extension, "layout" ) || !Q_stricmp( extension, "bsp" ) )
  845. {
  846. return true;
  847. }
  848. }
  849. return false;
  850. }