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.

1093 lines
32 KiB

  1. //========= Copyright 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 fopen
  19. #if defined( WIN32 ) && !defined( _X360 )
  20. #include "winlite.h"
  21. #include <WinInet.h>
  22. #endif
  23. #include <assert.h>
  24. #include "download.h"
  25. #include "tier0/platform.h"
  26. #include "download_internal.h"
  27. #include "client.h"
  28. #include "net_chan.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 "tier0/vcrmode.h"
  35. #include "cdll_engine_int.h"
  36. #include "../utils/bzip2/bzlib.h"
  37. #if defined( _X360 )
  38. #include "xbox/xbox_win32stubs.h"
  39. #endif
  40. #include "engine/idownloadsystem.h"
  41. // memdbgon must be the last include file in a .cpp file!!!
  42. #include "tier0/memdbgon.h"
  43. extern IFileSystem *g_pFileSystem;
  44. static const char *CacheDirectory = "cache";
  45. static const char *CacheFilename = "cache/DownloadCache.db";
  46. Color DownloadColor ( 0, 200, 100, 255 );
  47. Color DownloadErrorColor ( 200, 100, 100, 255 );
  48. Color DownloadCompleteColor ( 100, 200, 100, 255 );
  49. ConVar download_debug( "download_debug", "0", FCVAR_DONTRECORD ); // For debug printouts
  50. const char k_szDownloadPathID[] = "download";
  51. //--------------------------------------------------------------------------------------------------------------
  52. // Class Definitions
  53. //--------------------------------------------------------------------------------------------------------------
  54. //--------------------------------------------------------------------------------------------------------------
  55. // Purpose: Implements download cache manager
  56. //--------------------------------------------------------------------------------------------------------------
  57. class DownloadCache
  58. {
  59. public:
  60. DownloadCache();
  61. ~DownloadCache();
  62. void Init();
  63. void GetCachedData( RequestContext_t *rc ); ///< Loads cached data, if any
  64. void PersistToDisk( const RequestContext_t *rc ); ///< Writes out a completed download to disk
  65. void PersistToCache( const RequestContext_t *rc ); ///< Writes out a partial download (lost connection, user abort, etc) to cache
  66. private:
  67. KeyValues *m_cache;
  68. void GetCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] );
  69. void GenerateCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] );
  70. void BuildKeyNames( const char *gamePath ); ///< Convenience function to build the keys to index into m_cache
  71. char m_cachefileKey[BufferSize + 64];
  72. char m_timestampKey[BufferSize + 64];
  73. };
  74. static DownloadCache *TheDownloadCache = NULL;
  75. //--------------------------------------------------------------------------------------------------------------
  76. DownloadCache::DownloadCache()
  77. {
  78. m_cache = NULL;
  79. }
  80. //--------------------------------------------------------------------------------------------------------------
  81. DownloadCache::~DownloadCache()
  82. {
  83. }
  84. //--------------------------------------------------------------------------------------------------------------
  85. void DownloadCache::BuildKeyNames( const char *gamePath )
  86. {
  87. if ( !gamePath )
  88. {
  89. m_cachefileKey[0] = 0;
  90. m_timestampKey[0] = 0;
  91. return;
  92. }
  93. char *tmpGamePath = V_strdup( gamePath );
  94. char *tmp = tmpGamePath;
  95. while ( *tmp )
  96. {
  97. if ( *tmp == '/' || *tmp == '\\' )
  98. {
  99. *tmp = '_';
  100. }
  101. ++tmp;
  102. }
  103. Q_snprintf( m_cachefileKey, sizeof( m_cachefileKey ), "cachefile_%s", tmpGamePath );
  104. Q_snprintf( m_timestampKey, sizeof( m_timestampKey ), "timestamp_%s", tmpGamePath );
  105. delete[] tmpGamePath;
  106. }
  107. //--------------------------------------------------------------------------------------------------------------
  108. void DownloadCache::Init()
  109. {
  110. if ( m_cache )
  111. {
  112. m_cache->deleteThis();
  113. }
  114. m_cache = new KeyValues( "DownloadCache" );
  115. m_cache->LoadFromFile( g_pFileSystem, CacheFilename, NULL );
  116. g_pFileSystem->CreateDirHierarchy( CacheDirectory, "DEFAULT_WRITE_PATH" );
  117. }
  118. //--------------------------------------------------------------------------------------------------------------
  119. void DownloadCache::GetCachedData( RequestContext_t *rc )
  120. {
  121. if ( !m_cache )
  122. return;
  123. char cachePath[_MAX_PATH];
  124. GetCacheFilename( rc, cachePath );
  125. if ( !(*cachePath) )
  126. return;
  127. FileHandle_t fp = g_pFileSystem->Open( cachePath, "rb" );
  128. if ( fp == FILESYSTEM_INVALID_HANDLE )
  129. return;
  130. int size = g_pFileSystem->Size(fp);
  131. rc->cacheData = new unsigned char[size];
  132. int status = g_pFileSystem->Read( rc->cacheData, size, fp );
  133. g_pFileSystem->Close( fp );
  134. if ( !status )
  135. {
  136. delete[] rc->cacheData;
  137. rc->cacheData = NULL;
  138. }
  139. else
  140. {
  141. BuildKeyNames( rc->gamePath );
  142. rc->nBytesCached = size;
  143. strncpy( rc->cachedTimestamp, m_cache->GetString( m_timestampKey, "" ), BufferSize );
  144. }
  145. }
  146. //--------------------------------------------------------------------------------------------------------------
  147. /**
  148. * Takes a data stream compressed with bzip2, and writes it out to disk, uncompresses it, and deletes the
  149. * compressed version.
  150. */
  151. static bool DecompressBZipToDisk( const char *outFilename, const char *srcFilename, char *data, int bytesTotal )
  152. {
  153. if ( g_pFileSystem->FileExists( outFilename ) || !data || bytesTotal < 1 )
  154. {
  155. return false;
  156. }
  157. // Create the subdirs
  158. char * tmpDir = V_strdup( outFilename );
  159. COM_CreatePath( tmpDir );
  160. delete[] tmpDir;
  161. // open the file for writing
  162. char fullSrcPath[MAX_PATH];
  163. Q_MakeAbsolutePath( fullSrcPath, sizeof( fullSrcPath ), srcFilename, com_gamedir );
  164. if ( !g_pFileSystem->FileExists( fullSrcPath ) )
  165. {
  166. // Write out the .bz2 file, for simplest decompression
  167. FileHandle_t ifp = g_pFileSystem->Open( fullSrcPath, "wb" );
  168. if ( !ifp )
  169. {
  170. return false;
  171. }
  172. int bytesWritten = g_pFileSystem->Write( data, bytesTotal, ifp );
  173. g_pFileSystem->Close( ifp );
  174. if ( bytesWritten != bytesTotal )
  175. {
  176. // couldn't write out all of the .bz2 file
  177. g_pFileSystem->RemoveFile( srcFilename );
  178. return false;
  179. }
  180. }
  181. // Prepare the uncompressed filehandle
  182. FileHandle_t ofp = g_pFileSystem->Open( outFilename, "wb" );
  183. if ( !ofp )
  184. {
  185. g_pFileSystem->RemoveFile( srcFilename );
  186. return false;
  187. }
  188. // And decompress!
  189. const int OutBufSize = 65536;
  190. char buf[ OutBufSize ];
  191. BZFILE *bzfp = BZ2_bzopen( fullSrcPath, "rb" );
  192. int totalBytes = 0;
  193. bool bMapFile = false;
  194. char szOutFilenameBase[MAX_PATH];
  195. Q_FileBase( outFilename, szOutFilenameBase, sizeof( szOutFilenameBase ) );
  196. const char *pszMapName = cl.m_szLevelBaseName;
  197. if ( pszMapName && pszMapName[0] )
  198. {
  199. bMapFile = ( Q_stricmp( szOutFilenameBase, pszMapName ) == 0 );
  200. }
  201. while ( 1 )
  202. {
  203. int bytesRead = BZ2_bzread( bzfp, buf, OutBufSize );
  204. if ( bytesRead < 0 )
  205. {
  206. break; // error out
  207. }
  208. if ( bytesRead > 0 )
  209. {
  210. int bytesWritten = g_pFileSystem->Write( buf, bytesRead, ofp );
  211. if ( bytesWritten != bytesRead )
  212. {
  213. break; // error out
  214. }
  215. else
  216. {
  217. totalBytes += bytesWritten;
  218. if ( !bMapFile )
  219. {
  220. if ( totalBytes > MAX_FILE_SIZE )
  221. {
  222. ConDColorMsg( DownloadErrorColor, "DecompressBZipToDisk: '%s' too big (max %i bytes).\n", srcFilename, MAX_FILE_SIZE );
  223. break; // error out
  224. }
  225. }
  226. }
  227. }
  228. else
  229. {
  230. g_pFileSystem->Close( ofp );
  231. BZ2_bzclose( bzfp );
  232. g_pFileSystem->RemoveFile( srcFilename );
  233. return true;
  234. }
  235. }
  236. // We failed somewhere, so clean up and exit
  237. g_pFileSystem->Close( ofp );
  238. BZ2_bzclose( bzfp );
  239. g_pFileSystem->RemoveFile( srcFilename );
  240. g_pFileSystem->RemoveFile( outFilename );
  241. return false;
  242. }
  243. //--------------------------------------------------------------------------------------------------------------
  244. void DownloadCache::PersistToDisk( const RequestContext_t *rc )
  245. {
  246. if ( !m_cache )
  247. return;
  248. if ( rc && rc->data && rc->nBytesTotal )
  249. {
  250. char absPath[MAX_PATH];
  251. if ( rc->bIsBZ2 )
  252. {
  253. Q_StripExtension( rc->absLocalPath, absPath, sizeof( absPath ) );
  254. }
  255. else
  256. {
  257. Q_strncpy( absPath, rc->absLocalPath, sizeof( absPath ) );
  258. }
  259. if ( !g_pFileSystem->FileExists( absPath ) )
  260. {
  261. // Create the subdirs
  262. char * tmpDir = V_strdup( absPath );
  263. COM_CreatePath( tmpDir );
  264. delete[] tmpDir;
  265. bool success = false;
  266. if ( rc->bIsBZ2 )
  267. {
  268. success = DecompressBZipToDisk( absPath, rc->absLocalPath, reinterpret_cast< char * >(rc->data), rc->nBytesTotal );
  269. }
  270. else
  271. {
  272. FileHandle_t fp = g_pFileSystem->Open( absPath, "wb" );
  273. if ( fp )
  274. {
  275. g_pFileSystem->Write( rc->data, rc->nBytesTotal, fp );
  276. g_pFileSystem->Close( fp );
  277. success = true;
  278. }
  279. }
  280. if ( success )
  281. {
  282. // write succeeded. remove any old data from the cache.
  283. char cachePath[_MAX_PATH];
  284. GetCacheFilename( rc, cachePath );
  285. if ( cachePath[0] )
  286. {
  287. g_pFileSystem->RemoveFile( cachePath, NULL );
  288. }
  289. BuildKeyNames( rc->gamePath );
  290. KeyValues *kv = m_cache->FindKey( m_cachefileKey, false );
  291. if ( kv )
  292. {
  293. m_cache->RemoveSubKey( kv );
  294. }
  295. kv = m_cache->FindKey( m_timestampKey, false );
  296. if ( kv )
  297. {
  298. m_cache->RemoveSubKey( kv );
  299. }
  300. }
  301. }
  302. }
  303. m_cache->SaveToFile( g_pFileSystem, CacheFilename, NULL );
  304. }
  305. //--------------------------------------------------------------------------------------------------------------
  306. void DownloadCache::PersistToCache( const RequestContext_t *rc )
  307. {
  308. if ( !m_cache || !rc || !rc->data || !rc->nBytesTotal || !rc->nBytesCurrent )
  309. return;
  310. char cachePath[_MAX_PATH];
  311. GenerateCacheFilename( rc, cachePath );
  312. FileHandle_t fp = g_pFileSystem->Open( cachePath, "wb" );
  313. if ( fp )
  314. {
  315. g_pFileSystem->Write( rc->data, rc->nBytesCurrent, fp );
  316. g_pFileSystem->Close( fp );
  317. m_cache->SaveToFile( g_pFileSystem, CacheFilename, NULL );
  318. }
  319. }
  320. //--------------------------------------------------------------------------------------------------------------
  321. void DownloadCache::GetCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] )
  322. {
  323. BuildKeyNames( rc->gamePath );
  324. const char *path = m_cache->GetString( m_cachefileKey, NULL );
  325. if ( !path || strncmp( path, CacheDirectory, strlen(CacheDirectory) ) )
  326. {
  327. cachePath[0] = 0;
  328. return;
  329. }
  330. strncpy( cachePath, path, _MAX_PATH );
  331. cachePath[_MAX_PATH-1] = 0;
  332. }
  333. //--------------------------------------------------------------------------------------------------------------
  334. void DownloadCache::GenerateCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] )
  335. {
  336. GetCacheFilename( rc, cachePath );
  337. BuildKeyNames( rc->gamePath );
  338. m_cache->SetString( m_timestampKey, rc->cachedTimestamp );
  339. //ConDColorMsg( DownloadColor,"DownloadCache::GenerateCacheFilename() set %s = %s\n", 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. //ConDColorMsg( DownloadColor,"DownloadCache::GenerateCacheFilename() set %s = %s\n", m_cachefileKey, cachePath );
  356. return;
  357. }
  358. }
  359. // all 1000 were invalid?!?
  360. Q_snprintf( cachePath, _MAX_PATH, "%s/overflow", CacheDirectory );
  361. //ConDColorMsg( DownloadColor,"DownloadCache::GenerateCacheFilename() set %s = %s\n", m_cachefileKey, cachePath );
  362. m_cache->SetString( m_cachefileKey, cachePath );
  363. }
  364. }
  365. //--------------------------------------------------------------------------------------------------------------
  366. // Purpose: Implements download manager class
  367. //--------------------------------------------------------------------------------------------------------------
  368. class CDownloadManager
  369. {
  370. public:
  371. CDownloadManager();
  372. ~CDownloadManager();
  373. void Queue( const char *baseURL, const char *urlPath, const char *gamePath );
  374. void Stop() { Reset(); }
  375. int GetQueueSize() { return m_queuedRequests.Count(); }
  376. virtual bool Update(); ///< Monitors download thread, starts new downloads, and updates progress bar
  377. bool FileReceived( const char *filename, unsigned int requestID );
  378. bool FileDenied( const char *filename, unsigned int requestID );
  379. bool HasMapBeenDownloadedFromServer( const char *serverMapName );
  380. void MarkMapAsDownloadedFromServer( const char *serverMapName );
  381. private:
  382. void QueueInternal( const char *pBaseURL, const char *pURLPath, const char *pGamePath, bool bAsHttp, bool bCompressed );
  383. protected:
  384. virtual void UpdateProgressBar();
  385. virtual RequestContext_t *NewRequestContext();///< Call this to allocate a RequestContext_t - calls setup functions for derived classes
  386. virtual bool ShouldAttemptCompressedFileDownload() { return true; }
  387. virtual void SetupURLPath( RequestContext_t *pRequestContext, const char *pURLPath );
  388. virtual void SetupServerURL( RequestContext_t *pRequestContext );
  389. // Event handlers
  390. virtual void OnHttpConnecting( RequestContext_t *pRequestContext ) {}
  391. virtual void OnHttpFetch( RequestContext_t *pRequestContext ) {}
  392. virtual void OnHttpDone( RequestContext_t *pRequestContext ) {}
  393. virtual void OnHttpError( RequestContext_t *pRequestContext ) {}
  394. void Reset(); ///< Cancels any active download, as well as any queued ones
  395. void PruneCompletedRequests(); ///< Check download requests that have been completed to see if their threads have exited
  396. void CheckActiveDownload(); ///< Checks download status, and updates progress bar
  397. void StartNewDownload(); ///< Starts a new download if there are queued requests
  398. typedef CUtlVector< RequestContext_t * > RequestVector;
  399. RequestVector m_queuedRequests; ///< these are requests waiting to be spawned
  400. RequestContext_t *m_activeRequest; ///< this is the active request being downloaded in another thread
  401. RequestVector m_completedRequests; ///< these are waiting for the thread to exit
  402. int m_lastPercent; ///< last percent value the progress bar was updated with (to avoid spamming it)
  403. int m_totalRequests; ///< Total number of requests (used to set the top progress bar)
  404. int m_RequestIDCounter; ///< global increasing request ID counter
  405. typedef CUtlVector< char * > StrVector;
  406. StrVector m_downloadedMaps; ///< List of maps for which we have already tried to download assets.
  407. };
  408. //--------------------------------------------------------------------------------------------------------------
  409. static CDownloadManager s_DownloadManager;
  410. //--------------------------------------------------------------------------------------------------------------
  411. CDownloadManager::CDownloadManager()
  412. {
  413. m_activeRequest = NULL;
  414. m_lastPercent = 0;
  415. m_totalRequests = 0;
  416. }
  417. //--------------------------------------------------------------------------------------------------------------
  418. CDownloadManager::~CDownloadManager()
  419. {
  420. Reset();
  421. for ( int i=0; i<m_downloadedMaps.Count(); ++i )
  422. {
  423. delete[] m_downloadedMaps[i];
  424. }
  425. m_downloadedMaps.RemoveAll();
  426. }
  427. //--------------------------------------------------------------------------------------------------------------
  428. RequestContext_t *CDownloadManager::NewRequestContext()
  429. {
  430. return new RequestContext_t;
  431. }
  432. //--------------------------------------------------------------------------------------------------------------
  433. void CDownloadManager::SetupURLPath( RequestContext_t *pRequestContext, const char *pURLPath )
  434. {
  435. V_strcpy( pRequestContext->urlPath, pRequestContext->gamePath );
  436. }
  437. //--------------------------------------------------------------------------------------------------------------
  438. void CDownloadManager::SetupServerURL( RequestContext_t *pRequestContext )
  439. {
  440. Q_strncpy( pRequestContext->serverURL, cl.m_NetChannel->GetRemoteAddress().ToString(), BufferSize );
  441. }
  442. //--------------------------------------------------------------------------------------------------------------
  443. bool CDownloadManager::HasMapBeenDownloadedFromServer( const char *serverMapName )
  444. {
  445. if ( !serverMapName )
  446. return false;
  447. for ( int i=0; i<m_downloadedMaps.Count(); ++i )
  448. {
  449. const char *oldServerMapName = m_downloadedMaps[i];
  450. if ( oldServerMapName && !stricmp( serverMapName, oldServerMapName ) )
  451. {
  452. return true;
  453. }
  454. }
  455. return false;
  456. }
  457. bool CDownloadManager::FileDenied( const char *filename, unsigned int requestID )
  458. {
  459. if ( !m_activeRequest )
  460. return false;
  461. if ( m_activeRequest->nRequestID != requestID )
  462. return false;
  463. if ( m_activeRequest->bAsHTTP )
  464. return false;
  465. if ( download_debug.GetBool() )
  466. {
  467. ConDColorMsg( DownloadErrorColor, "Error downloading %s\n", m_activeRequest->absLocalPath );
  468. }
  469. UpdateProgressBar();
  470. // try to download the next file
  471. m_completedRequests.AddToTail( m_activeRequest );
  472. m_activeRequest = NULL;
  473. return true;
  474. }
  475. bool CDownloadManager::FileReceived( const char *filename, unsigned int requestID )
  476. {
  477. if ( !m_activeRequest )
  478. return false;
  479. if ( m_activeRequest->nRequestID != requestID )
  480. return false;
  481. if ( m_activeRequest->bAsHTTP )
  482. return false;
  483. if ( download_debug.GetBool() )
  484. {
  485. ConDColorMsg( DownloadCompleteColor, "Download finished!\n" );
  486. }
  487. UpdateProgressBar();
  488. m_completedRequests.AddToTail( m_activeRequest );
  489. m_activeRequest = NULL;
  490. return true;
  491. }
  492. //--------------------------------------------------------------------------------------------------------------
  493. void CDownloadManager::MarkMapAsDownloadedFromServer( const char *serverMapName )
  494. {
  495. if ( !serverMapName )
  496. return;
  497. if ( HasMapBeenDownloadedFromServer( serverMapName ) )
  498. return;
  499. m_downloadedMaps.AddToTail( V_strdup( serverMapName ) );
  500. return;
  501. }
  502. //--------------------------------------------------------------------------------------------------------------
  503. void CDownloadManager::QueueInternal( const char *pBaseURL, const char *pURLPath, const char *pGamePath,
  504. bool bAsHttp, bool bCompressed )
  505. {
  506. // NOTE: Assumes valid game path (i.e. IsGamePathValidAndSafe() has been called already)
  507. ++m_totalRequests;
  508. // Initialize the download cache if necessary
  509. if ( !TheDownloadCache )
  510. {
  511. TheDownloadCache = new DownloadCache;
  512. TheDownloadCache->Init();
  513. }
  514. // Create a new context and add queue it
  515. RequestContext_t *rc = NewRequestContext();
  516. m_queuedRequests.AddToTail( rc );
  517. rc->bIsBZ2 = bCompressed;
  518. rc->bAsHTTP = bAsHttp;
  519. rc->status = HTTP_CONNECTING;
  520. // Setup base path. We put it in the "download" search path, if they have set one
  521. char szBasePath[ MAX_PATH ];
  522. if ( g_pFileSystem->GetSearchPath( k_szDownloadPathID, false, szBasePath, sizeof(szBasePath) ) > 0 )
  523. {
  524. char *split = V_strstr( szBasePath, ";" );
  525. if ( split != NULL )
  526. {
  527. Warning( "Multiple download search paths? Check gameinfo.txt" );
  528. *split = '\0';
  529. }
  530. }
  531. // Otherwise, put it in the game dir
  532. if ( szBasePath[0] == '\0' )
  533. V_strcpy_safe( szBasePath, com_gamedir );
  534. // Setup game path
  535. V_strcpy_safe( rc->gamePath, pGamePath );
  536. if ( bCompressed )
  537. {
  538. V_strcat_safe( rc->gamePath, ".bz2" );
  539. }
  540. Q_FixSlashes( rc->gamePath, '/' ); // only matters for debug prints, which are full URLS, so we want forward slashes
  541. // NOTE: Loose files on disk must always be lowercase! At least on Linux they HAVE to be,
  542. // but we do the same thing on Windows to keep things consistent.
  543. char szGamePathLower[MAX_PATH];
  544. V_strcpy_safe( szGamePathLower, rc->gamePath );
  545. V_strlower( szGamePathLower );
  546. // Now set the full absolute path. Why does the file system not provide a convenient method to
  547. // do stuff like this?
  548. V_strcpy_safe( rc->absLocalPath, szBasePath );
  549. V_AppendSlash( rc->absLocalPath, sizeof(rc->absLocalPath) );
  550. V_strcat_safe( rc->absLocalPath, szGamePathLower );
  551. V_FixSlashes( rc->absLocalPath );
  552. // Setup base URL if necessary
  553. if ( bAsHttp )
  554. {
  555. V_strcpy_safe( rc->baseURL, pBaseURL );
  556. V_StripTrailingSlash( rc->baseURL );
  557. V_strcat_safe( rc->baseURL, "/" );
  558. }
  559. // Call virtual methods for setting up additional context info
  560. SetupURLPath( rc, pURLPath );
  561. SetupServerURL( rc );
  562. if ( download_debug.GetBool() )
  563. {
  564. ConDColorMsg( DownloadColor, "Queueing %s%s.\n", rc->baseURL, pGamePath );
  565. }
  566. // Invoke the callback if appropriate
  567. if ( bAsHttp )
  568. {
  569. OnHttpConnecting( rc );
  570. }
  571. }
  572. void CDownloadManager::Queue( const char *baseURL, const char *urlPath, const char *gamePath )
  573. {
  574. if ( !CL_IsGamePathValidAndSafeForDownload( gamePath ) )
  575. return;
  576. // Don't download existing files
  577. if ( g_pFileSystem->FileExists( gamePath ) )
  578. return;
  579. #ifndef _DEBUG
  580. if ( sv.IsActive() )
  581. {
  582. return; // don't try to download things for the local server (in case a map is missing sounds etc that
  583. // aren't needed to play.
  584. }
  585. #endif
  586. // HTTP or NetChan?
  587. bool bAsHTTP = baseURL && ( !Q_strnicmp( baseURL, "http://", 7 ) || !Q_strnicmp( baseURL, "https://", 8 ) );
  588. // Queue up an HTTP download of the bzipped asset, in case it exists.
  589. // When a bzipped download finishes, we'll uncompress the file to it's
  590. // original destination, and the queued download of the uncompressed
  591. // file will abort.
  592. if ( bAsHTTP && ShouldAttemptCompressedFileDownload() && !g_pFileSystem->FileExists( va( "%s.bz2", gamePath ) ) )
  593. {
  594. QueueInternal( baseURL, urlPath, gamePath, true, true );
  595. }
  596. // Queue up the straight, uncompressed version
  597. QueueInternal( baseURL, urlPath, gamePath, bAsHTTP, false );
  598. if ( download_debug.GetBool() )
  599. {
  600. ConDColorMsg( DownloadColor, "Queueing %s%s.\n", baseURL, gamePath );
  601. }
  602. }
  603. //--------------------------------------------------------------------------------------------------------------
  604. void CDownloadManager::Reset()
  605. {
  606. // ask the active request to bail
  607. if ( m_activeRequest )
  608. {
  609. if ( download_debug.GetBool() )
  610. {
  611. ConDColorMsg( DownloadColor, "Aborting download of %s\n", m_activeRequest->gamePath );
  612. }
  613. if ( m_activeRequest->nBytesTotal && m_activeRequest->nBytesCurrent )
  614. {
  615. // Persist partial data to cache
  616. TheDownloadCache->PersistToCache( m_activeRequest );
  617. }
  618. m_activeRequest->shouldStop = true;
  619. m_completedRequests.AddToTail( m_activeRequest );
  620. m_activeRequest = NULL;
  621. //TODO: StopLoadingProgressBar();
  622. }
  623. // clear out any queued requests
  624. for ( int i=0; i<m_queuedRequests.Count(); ++i )
  625. {
  626. if ( download_debug.GetBool() )
  627. {
  628. ConDColorMsg( DownloadColor, "Discarding queued download of %s\n", m_queuedRequests[i]->gamePath );
  629. }
  630. delete m_queuedRequests[i];
  631. }
  632. m_queuedRequests.RemoveAll();
  633. if ( TheDownloadCache )
  634. {
  635. delete TheDownloadCache;
  636. TheDownloadCache = NULL;
  637. }
  638. m_lastPercent = 0;
  639. m_totalRequests = 0;
  640. }
  641. //--------------------------------------------------------------------------------------------------------------
  642. // Check download requests that have been completed to see if their threads have exited
  643. void CDownloadManager::PruneCompletedRequests()
  644. {
  645. for ( int i=m_completedRequests.Count()-1; i>=0; --i )
  646. {
  647. if ( m_completedRequests[i]->threadDone || !m_completedRequests[i]->bAsHTTP )
  648. {
  649. if ( m_completedRequests[i]->cacheData )
  650. {
  651. delete[] m_completedRequests[i]->cacheData;
  652. }
  653. delete m_completedRequests[i];
  654. m_completedRequests.Remove( i );
  655. }
  656. }
  657. }
  658. //--------------------------------------------------------------------------------------------------------------
  659. // Checks download status, and updates progress bar
  660. void CDownloadManager::CheckActiveDownload()
  661. {
  662. if ( !m_activeRequest )
  663. return;
  664. if ( !m_activeRequest->bAsHTTP )
  665. {
  666. UpdateProgressBar();
  667. return;
  668. }
  669. // check active request for completion / error / progress update
  670. switch ( m_activeRequest->status )
  671. {
  672. case HTTP_DONE:
  673. if ( download_debug.GetBool() )
  674. {
  675. ConDColorMsg( DownloadCompleteColor, "Download finished!\n" );
  676. }
  677. UpdateProgressBar();
  678. OnHttpDone( m_activeRequest );
  679. if ( m_activeRequest->nBytesTotal )
  680. {
  681. // Persist complete data to disk, and remove cache entry
  682. //TODO: SetSecondaryProgressBarText( m_activeRequest->gamePath );
  683. TheDownloadCache->PersistToDisk( m_activeRequest );
  684. m_activeRequest->shouldStop = true;
  685. m_completedRequests.AddToTail( m_activeRequest );
  686. m_activeRequest = NULL;
  687. if ( !m_queuedRequests.Count() )
  688. {
  689. //TODO: StopLoadingProgressBar();
  690. //TODO: Cbuf_AddText("retry\n");
  691. }
  692. }
  693. break;
  694. case HTTP_ERROR:
  695. if ( download_debug.GetBool() )
  696. {
  697. ConDColorMsg( DownloadErrorColor, "Error downloading %s%s\n", m_activeRequest->baseURL, m_activeRequest->gamePath );
  698. }
  699. UpdateProgressBar();
  700. // try to download the next file
  701. m_activeRequest->shouldStop = true;
  702. m_completedRequests.AddToTail( m_activeRequest );
  703. OnHttpError( m_activeRequest );
  704. m_activeRequest = NULL;
  705. if ( !m_queuedRequests.Count() )
  706. {
  707. //TODO: StopLoadingProgressBar();
  708. //TODO: Cbuf_AddText("retry\n");
  709. }
  710. break;
  711. case HTTP_FETCH:
  712. UpdateProgressBar();
  713. // Update progress bar
  714. //TODO: SetSecondaryProgressBarText( m_activeRequest->gamePath );
  715. if ( m_activeRequest->nBytesTotal )
  716. {
  717. int percent = ( m_activeRequest->nBytesCurrent * 100 / m_activeRequest->nBytesTotal );
  718. if ( percent != m_lastPercent )
  719. {
  720. if ( download_debug.GetBool() )
  721. {
  722. ConDColorMsg( DownloadColor, "Downloading %s%s: %3.3d%% - %d of %d bytes\n",
  723. m_activeRequest->baseURL, m_activeRequest->gamePath,
  724. percent, m_activeRequest->nBytesCurrent, m_activeRequest->nBytesTotal );
  725. }
  726. m_lastPercent = percent;
  727. //TODO: SetSecondaryProgressBar( m_lastPercent * 0.01f );
  728. }
  729. }
  730. OnHttpFetch( m_activeRequest );
  731. break;
  732. }
  733. }
  734. //--------------------------------------------------------------------------------------------------------------
  735. // Starts a new download if there are queued requests
  736. void CDownloadManager::StartNewDownload()
  737. {
  738. if ( m_activeRequest || !m_queuedRequests.Count() )
  739. return;
  740. while ( !m_activeRequest && m_queuedRequests.Count() )
  741. {
  742. // Remove one request from the queue and make it active
  743. m_activeRequest = m_queuedRequests[0];
  744. m_queuedRequests.Remove( 0 );
  745. if ( g_pFileSystem->FileExists( m_activeRequest->absLocalPath ) )
  746. {
  747. if ( download_debug.GetBool() )
  748. {
  749. ConDColorMsg( DownloadColor, "Skipping existing file %s%s.\n", m_activeRequest->baseURL, m_activeRequest->gamePath );
  750. }
  751. m_activeRequest->shouldStop = true;
  752. m_activeRequest->threadDone = true;
  753. m_completedRequests.AddToTail( m_activeRequest );
  754. m_activeRequest = NULL;
  755. }
  756. }
  757. if ( !m_activeRequest )
  758. return;
  759. if ( g_pFileSystem->FileExists( m_activeRequest->absLocalPath ) )
  760. {
  761. m_activeRequest->shouldStop = true;
  762. m_activeRequest->threadDone = true;
  763. m_completedRequests.AddToTail( m_activeRequest );
  764. m_activeRequest = NULL;
  765. return; // don't download existing files
  766. }
  767. if ( m_activeRequest->bAsHTTP )
  768. {
  769. // Check cache for partial match
  770. TheDownloadCache->GetCachedData( m_activeRequest );
  771. //TODO: ContinueLoadingProgressBar( "Http", m_totalRequests - m_queuedRequests.Count(), 0.0f );
  772. //TODO: SetLoadingProgressBarStatusText( "#GameUI_VerifyingAndDownloading" );
  773. //TODO: SetSecondaryProgressBarText( m_activeRequest->gamePath );
  774. //TODO: SetSecondaryProgressBar( 0.0f );
  775. UpdateProgressBar();
  776. if ( download_debug.GetBool() )
  777. {
  778. ConDColorMsg( DownloadColor, "Downloading %s%s.\n", m_activeRequest->baseURL, m_activeRequest->gamePath );
  779. }
  780. m_lastPercent = 0;
  781. // Start the thread
  782. DWORD threadID;
  783. VCRHook_CreateThread(NULL, 0,
  784. #ifdef POSIX
  785. (void *)
  786. #endif
  787. DownloadThread, m_activeRequest, 0, (unsigned long int *)&threadID );
  788. ThreadDetach( ( ThreadHandle_t )threadID );
  789. }
  790. else
  791. {
  792. UpdateProgressBar();
  793. if ( download_debug.GetBool() )
  794. {
  795. ConDColorMsg( DownloadColor, "Downloading %s.\n", m_activeRequest->gamePath );
  796. }
  797. m_lastPercent = 0;
  798. m_activeRequest->nRequestID = cl.m_NetChannel->RequestFile( m_activeRequest->gamePath );
  799. }
  800. }
  801. //--------------------------------------------------------------------------------------------------------------
  802. void CDownloadManager::UpdateProgressBar()
  803. {
  804. if ( !m_activeRequest )
  805. {
  806. return;
  807. }
  808. wchar_t filenameBuf[MAX_OSPATH];
  809. float progress = 0.0f;
  810. if ( m_activeRequest->bAsHTTP )
  811. {
  812. int overallPercent = (m_totalRequests - m_queuedRequests.Count() - 1) * 100 / m_totalRequests;
  813. int filePercent = 0;
  814. if ( m_activeRequest->nBytesTotal > 0 )
  815. {
  816. filePercent = ( m_activeRequest->nBytesCurrent * 100 / m_activeRequest->nBytesTotal );
  817. }
  818. progress = (overallPercent + filePercent * 1.0f / m_totalRequests) * 0.01f;
  819. }
  820. else
  821. {
  822. int received, total;
  823. cl.m_NetChannel->GetStreamProgress( FLOW_INCOMING, &received, &total );
  824. progress = (float)(received)/(float)(total);
  825. }
  826. #ifndef DEDICATED
  827. _snwprintf( filenameBuf, 256, L"Downloading %hs", m_activeRequest->gamePath );
  828. EngineVGui()->UpdateCustomProgressBar( progress, filenameBuf );
  829. #endif
  830. }
  831. //--------------------------------------------------------------------------------------------------------------
  832. // Monitors download thread, starts new downloads, and updates progress bar
  833. bool CDownloadManager::Update()
  834. {
  835. PruneCompletedRequests();
  836. CheckActiveDownload();
  837. StartNewDownload();
  838. return m_activeRequest != NULL;
  839. }
  840. //--------------------------------------------------------------------------------------------------------------
  841. // Externally-visible function definitions
  842. //--------------------------------------------------------------------------------------------------------------
  843. //--------------------------------------------------------------------------------------------------------------
  844. bool CL_DownloadUpdate(void)
  845. {
  846. return s_DownloadManager.Update();
  847. }
  848. //--------------------------------------------------------------------------------------------------------------
  849. void CL_HTTPStop_f(void)
  850. {
  851. s_DownloadManager.Stop();
  852. }
  853. bool CL_FileReceived( const char *filename, unsigned int requestID )
  854. {
  855. return s_DownloadManager.FileReceived( filename, requestID );
  856. }
  857. bool CL_FileDenied( const char *filename, unsigned int requestID )
  858. {
  859. return s_DownloadManager.FileDenied( filename, requestID );
  860. }
  861. //--------------------------------------------------------------------------------------------------------------
  862. extern ConVar sv_downloadurl;
  863. void CL_QueueDownload( const char *filename )
  864. {
  865. s_DownloadManager.Queue( sv_downloadurl.GetString(), NULL, filename );
  866. }
  867. //--------------------------------------------------------------------------------------------------------------
  868. int CL_GetDownloadQueueSize(void)
  869. {
  870. return s_DownloadManager.GetQueueSize();
  871. }
  872. //--------------------------------------------------------------------------------------------------------------
  873. int CL_CanUseHTTPDownload(void)
  874. {
  875. if ( sv_downloadurl.GetString()[0] )
  876. {
  877. const char *serverMapName = va( "%s:%s", sv_downloadurl.GetString(), cl.m_szLevelFileName );
  878. return !s_DownloadManager.HasMapBeenDownloadedFromServer( serverMapName );
  879. }
  880. return 0;
  881. }
  882. //--------------------------------------------------------------------------------------------------------------
  883. void CL_MarkMapAsUsingHTTPDownload(void)
  884. {
  885. const char *serverMapName = va( "%s:%s", sv_downloadurl.GetString(), cl.m_szLevelFileName );
  886. s_DownloadManager.MarkMapAsDownloadedFromServer( serverMapName );
  887. }
  888. //--------------------------------------------------------------------------------------------------------------
  889. bool CL_IsGamePathValidAndSafeForDownload( const char *pGamePath )
  890. {
  891. if ( !CNetChan::IsValidFileForTransfer( pGamePath ) )
  892. return false;
  893. return true;
  894. }
  895. //--------------------------------------------------------------------------------------------------------------
  896. class CDownloadSystem : public IDownloadSystem
  897. {
  898. public:
  899. virtual DWORD CreateDownloadThread( RequestContext_t *pContext )
  900. {
  901. DWORD nThreadID;
  902. VCRHook_CreateThread(NULL, 0,
  903. #ifdef POSIX
  904. (void*)
  905. #endif
  906. DownloadThread, pContext, 0, (unsigned long int *)&nThreadID );
  907. ThreadDetach( ( ThreadHandle_t )nThreadID );
  908. return nThreadID;
  909. }
  910. };
  911. //--------------------------------------------------------------------------------------------------------------
  912. static CDownloadSystem s_DownloadSystem;
  913. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDownloadSystem, IDownloadSystem, INTERFACEVERSION_DOWNLOADSYSTEM, s_DownloadSystem );
  914. //--------------------------------------------------------------------------------------------------------------