diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/download.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/download.cpp')
| -rw-r--r-- | engine/download.cpp | 1093 |
1 files changed, 1093 insertions, 0 deletions
diff --git a/engine/download.cpp b/engine/download.cpp new file mode 100644 index 0000000..8714b06 --- /dev/null +++ b/engine/download.cpp @@ -0,0 +1,1093 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//-------------------------------------------------------------------------------------------------------------- +// download.cpp +// +// Implementation file for optional HTTP asset downloading +// Author: Matthew D. Campbell ([email protected]), 2004 +//-------------------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------------------- +// Includes +//-------------------------------------------------------------------------------------------------------------- + +// fopen is needed for the bzip code +#undef fopen + +#if defined( WIN32 ) && !defined( _X360 ) +#include "winlite.h" +#include <WinInet.h> +#endif + +#include <assert.h> + +#include "download.h" +#include "tier0/platform.h" +#include "download_internal.h" + +#include "client.h" +#include "net_chan.h" + +#include <KeyValues.h> +#include "filesystem.h" +#include "filesystem_engine.h" +#include "server.h" +#include "vgui_baseui_interface.h" +#include "tier0/vcrmode.h" +#include "cdll_engine_int.h" + +#include "../utils/bzip2/bzlib.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +#include "engine/idownloadsystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IFileSystem *g_pFileSystem; +static const char *CacheDirectory = "cache"; +static const char *CacheFilename = "cache/DownloadCache.db"; +Color DownloadColor ( 0, 200, 100, 255 ); +Color DownloadErrorColor ( 200, 100, 100, 255 ); +Color DownloadCompleteColor ( 100, 200, 100, 255 ); + +ConVar download_debug( "download_debug", "0", FCVAR_DONTRECORD ); // For debug printouts + +const char k_szDownloadPathID[] = "download"; + +//-------------------------------------------------------------------------------------------------------------- +// Class Definitions +//-------------------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------------------- +// Purpose: Implements download cache manager +//-------------------------------------------------------------------------------------------------------------- +class DownloadCache +{ +public: + DownloadCache(); + ~DownloadCache(); + void Init(); + + void GetCachedData( RequestContext_t *rc ); ///< Loads cached data, if any + void PersistToDisk( const RequestContext_t *rc ); ///< Writes out a completed download to disk + void PersistToCache( const RequestContext_t *rc ); ///< Writes out a partial download (lost connection, user abort, etc) to cache + +private: + KeyValues *m_cache; + + void GetCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] ); + void GenerateCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] ); + + void BuildKeyNames( const char *gamePath ); ///< Convenience function to build the keys to index into m_cache + char m_cachefileKey[BufferSize + 64]; + char m_timestampKey[BufferSize + 64]; +}; +static DownloadCache *TheDownloadCache = NULL; + +//-------------------------------------------------------------------------------------------------------------- +DownloadCache::DownloadCache() +{ + m_cache = NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +DownloadCache::~DownloadCache() +{ +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::BuildKeyNames( const char *gamePath ) +{ + if ( !gamePath ) + { + m_cachefileKey[0] = 0; + m_timestampKey[0] = 0; + return; + } + + char *tmpGamePath = V_strdup( gamePath ); + char *tmp = tmpGamePath; + while ( *tmp ) + { + if ( *tmp == '/' || *tmp == '\\' ) + { + *tmp = '_'; + } + ++tmp; + } + Q_snprintf( m_cachefileKey, sizeof( m_cachefileKey ), "cachefile_%s", tmpGamePath ); + Q_snprintf( m_timestampKey, sizeof( m_timestampKey ), "timestamp_%s", tmpGamePath ); + + delete[] tmpGamePath; +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::Init() +{ + if ( m_cache ) + { + m_cache->deleteThis(); + } + + m_cache = new KeyValues( "DownloadCache" ); + m_cache->LoadFromFile( g_pFileSystem, CacheFilename, NULL ); + g_pFileSystem->CreateDirHierarchy( CacheDirectory, "DEFAULT_WRITE_PATH" ); +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::GetCachedData( RequestContext_t *rc ) +{ + if ( !m_cache ) + return; + + char cachePath[_MAX_PATH]; + GetCacheFilename( rc, cachePath ); + + if ( !(*cachePath) ) + return; + + FileHandle_t fp = g_pFileSystem->Open( cachePath, "rb" ); + + if ( fp == FILESYSTEM_INVALID_HANDLE ) + return; + + int size = g_pFileSystem->Size(fp); + rc->cacheData = new unsigned char[size]; + int status = g_pFileSystem->Read( rc->cacheData, size, fp ); + g_pFileSystem->Close( fp ); + if ( !status ) + { + delete[] rc->cacheData; + rc->cacheData = NULL; + } + else + { + BuildKeyNames( rc->gamePath ); + rc->nBytesCached = size; + strncpy( rc->cachedTimestamp, m_cache->GetString( m_timestampKey, "" ), BufferSize ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Takes a data stream compressed with bzip2, and writes it out to disk, uncompresses it, and deletes the + * compressed version. + */ +static bool DecompressBZipToDisk( const char *outFilename, const char *srcFilename, char *data, int bytesTotal ) +{ + if ( g_pFileSystem->FileExists( outFilename ) || !data || bytesTotal < 1 ) + { + return false; + } + + // Create the subdirs + char * tmpDir = V_strdup( outFilename ); + COM_CreatePath( tmpDir ); + delete[] tmpDir; + + // open the file for writing + char fullSrcPath[MAX_PATH]; + Q_MakeAbsolutePath( fullSrcPath, sizeof( fullSrcPath ), srcFilename, com_gamedir ); + + if ( !g_pFileSystem->FileExists( fullSrcPath ) ) + { + // Write out the .bz2 file, for simplest decompression + FileHandle_t ifp = g_pFileSystem->Open( fullSrcPath, "wb" ); + if ( !ifp ) + { + return false; + } + int bytesWritten = g_pFileSystem->Write( data, bytesTotal, ifp ); + g_pFileSystem->Close( ifp ); + if ( bytesWritten != bytesTotal ) + { + // couldn't write out all of the .bz2 file + g_pFileSystem->RemoveFile( srcFilename ); + return false; + } + } + + // Prepare the uncompressed filehandle + FileHandle_t ofp = g_pFileSystem->Open( outFilename, "wb" ); + if ( !ofp ) + { + g_pFileSystem->RemoveFile( srcFilename ); + return false; + } + + // And decompress! + const int OutBufSize = 65536; + char buf[ OutBufSize ]; + BZFILE *bzfp = BZ2_bzopen( fullSrcPath, "rb" ); + int totalBytes = 0; + + bool bMapFile = false; + char szOutFilenameBase[MAX_PATH]; + Q_FileBase( outFilename, szOutFilenameBase, sizeof( szOutFilenameBase ) ); + const char *pszMapName = cl.m_szLevelBaseName; + if ( pszMapName && pszMapName[0] ) + { + bMapFile = ( Q_stricmp( szOutFilenameBase, pszMapName ) == 0 ); + } + + while ( 1 ) + { + int bytesRead = BZ2_bzread( bzfp, buf, OutBufSize ); + if ( bytesRead < 0 ) + { + break; // error out + } + + if ( bytesRead > 0 ) + { + int bytesWritten = g_pFileSystem->Write( buf, bytesRead, ofp ); + if ( bytesWritten != bytesRead ) + { + break; // error out + } + else + { + totalBytes += bytesWritten; + if ( !bMapFile ) + { + if ( totalBytes > MAX_FILE_SIZE ) + { + ConDColorMsg( DownloadErrorColor, "DecompressBZipToDisk: '%s' too big (max %i bytes).\n", srcFilename, MAX_FILE_SIZE ); + break; // error out + } + } + } + } + else + { + g_pFileSystem->Close( ofp ); + BZ2_bzclose( bzfp ); + g_pFileSystem->RemoveFile( srcFilename ); + return true; + } + } + + // We failed somewhere, so clean up and exit + g_pFileSystem->Close( ofp ); + BZ2_bzclose( bzfp ); + g_pFileSystem->RemoveFile( srcFilename ); + g_pFileSystem->RemoveFile( outFilename ); + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::PersistToDisk( const RequestContext_t *rc ) +{ + if ( !m_cache ) + return; + + if ( rc && rc->data && rc->nBytesTotal ) + { + char absPath[MAX_PATH]; + if ( rc->bIsBZ2 ) + { + Q_StripExtension( rc->absLocalPath, absPath, sizeof( absPath ) ); + } + else + { + Q_strncpy( absPath, rc->absLocalPath, sizeof( absPath ) ); + } + + if ( !g_pFileSystem->FileExists( absPath ) ) + { + // Create the subdirs + char * tmpDir = V_strdup( absPath ); + COM_CreatePath( tmpDir ); + delete[] tmpDir; + + bool success = false; + if ( rc->bIsBZ2 ) + { + success = DecompressBZipToDisk( absPath, rc->absLocalPath, reinterpret_cast< char * >(rc->data), rc->nBytesTotal ); + } + else + { + FileHandle_t fp = g_pFileSystem->Open( absPath, "wb" ); + if ( fp ) + { + g_pFileSystem->Write( rc->data, rc->nBytesTotal, fp ); + g_pFileSystem->Close( fp ); + success = true; + } + } + + if ( success ) + { + // write succeeded. remove any old data from the cache. + char cachePath[_MAX_PATH]; + GetCacheFilename( rc, cachePath ); + if ( cachePath[0] ) + { + g_pFileSystem->RemoveFile( cachePath, NULL ); + } + + BuildKeyNames( rc->gamePath ); + KeyValues *kv = m_cache->FindKey( m_cachefileKey, false ); + if ( kv ) + { + m_cache->RemoveSubKey( kv ); + } + kv = m_cache->FindKey( m_timestampKey, false ); + if ( kv ) + { + m_cache->RemoveSubKey( kv ); + } + } + } + } + + m_cache->SaveToFile( g_pFileSystem, CacheFilename, NULL ); +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::PersistToCache( const RequestContext_t *rc ) +{ + if ( !m_cache || !rc || !rc->data || !rc->nBytesTotal || !rc->nBytesCurrent ) + return; + + char cachePath[_MAX_PATH]; + GenerateCacheFilename( rc, cachePath ); + + FileHandle_t fp = g_pFileSystem->Open( cachePath, "wb" ); + if ( fp ) + { + g_pFileSystem->Write( rc->data, rc->nBytesCurrent, fp ); + g_pFileSystem->Close( fp ); + + m_cache->SaveToFile( g_pFileSystem, CacheFilename, NULL ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::GetCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] ) +{ + BuildKeyNames( rc->gamePath ); + const char *path = m_cache->GetString( m_cachefileKey, NULL ); + if ( !path || strncmp( path, CacheDirectory, strlen(CacheDirectory) ) ) + { + cachePath[0] = 0; + return; + } + strncpy( cachePath, path, _MAX_PATH ); + cachePath[_MAX_PATH-1] = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +void DownloadCache::GenerateCacheFilename( const RequestContext_t *rc, char cachePath[_MAX_PATH] ) +{ + GetCacheFilename( rc, cachePath ); + BuildKeyNames( rc->gamePath ); + + m_cache->SetString( m_timestampKey, rc->cachedTimestamp ); + //ConDColorMsg( DownloadColor,"DownloadCache::GenerateCacheFilename() set %s = %s\n", m_timestampKey, rc->cachedTimestamp ); + + if ( !*cachePath ) + { + const char * lastSlash = strrchr( rc->gamePath, '/' ); + const char * lastBackslash = strrchr( rc->gamePath, '\\' ); + const char *gameFilename = rc->gamePath; + if ( lastSlash || lastBackslash ) + { + gameFilename = max( lastSlash, lastBackslash ) + 1; + } + for( int i=0; i<1000; ++i ) + { + Q_snprintf( cachePath, _MAX_PATH, "%s/%s%4.4d", CacheDirectory, gameFilename, i ); + if ( !g_pFileSystem->FileExists( cachePath ) ) + { + m_cache->SetString( m_cachefileKey, cachePath ); + //ConDColorMsg( DownloadColor,"DownloadCache::GenerateCacheFilename() set %s = %s\n", m_cachefileKey, cachePath ); + return; + } + } + // all 1000 were invalid?!? + Q_snprintf( cachePath, _MAX_PATH, "%s/overflow", CacheDirectory ); + //ConDColorMsg( DownloadColor,"DownloadCache::GenerateCacheFilename() set %s = %s\n", m_cachefileKey, cachePath ); + m_cache->SetString( m_cachefileKey, cachePath ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +// Purpose: Implements download manager class +//-------------------------------------------------------------------------------------------------------------- +class CDownloadManager +{ +public: + CDownloadManager(); + ~CDownloadManager(); + + void Queue( const char *baseURL, const char *urlPath, const char *gamePath ); + void Stop() { Reset(); } + int GetQueueSize() { return m_queuedRequests.Count(); } + + virtual bool Update(); ///< Monitors download thread, starts new downloads, and updates progress bar + + bool FileReceived( const char *filename, unsigned int requestID ); + bool FileDenied( const char *filename, unsigned int requestID ); + + bool HasMapBeenDownloadedFromServer( const char *serverMapName ); + void MarkMapAsDownloadedFromServer( const char *serverMapName ); + +private: + void QueueInternal( const char *pBaseURL, const char *pURLPath, const char *pGamePath, bool bAsHttp, bool bCompressed ); + +protected: + virtual void UpdateProgressBar(); + + virtual RequestContext_t *NewRequestContext();///< Call this to allocate a RequestContext_t - calls setup functions for derived classes + virtual bool ShouldAttemptCompressedFileDownload() { return true; } + virtual void SetupURLPath( RequestContext_t *pRequestContext, const char *pURLPath ); + virtual void SetupServerURL( RequestContext_t *pRequestContext ); + + // Event handlers + virtual void OnHttpConnecting( RequestContext_t *pRequestContext ) {} + virtual void OnHttpFetch( RequestContext_t *pRequestContext ) {} + virtual void OnHttpDone( RequestContext_t *pRequestContext ) {} + virtual void OnHttpError( RequestContext_t *pRequestContext ) {} + + void Reset(); ///< Cancels any active download, as well as any queued ones + + void PruneCompletedRequests(); ///< Check download requests that have been completed to see if their threads have exited + void CheckActiveDownload(); ///< Checks download status, and updates progress bar + void StartNewDownload(); ///< Starts a new download if there are queued requests + + typedef CUtlVector< RequestContext_t * > RequestVector; + RequestVector m_queuedRequests; ///< these are requests waiting to be spawned + RequestContext_t *m_activeRequest; ///< this is the active request being downloaded in another thread + RequestVector m_completedRequests; ///< these are waiting for the thread to exit + + int m_lastPercent; ///< last percent value the progress bar was updated with (to avoid spamming it) + int m_totalRequests; ///< Total number of requests (used to set the top progress bar) + + int m_RequestIDCounter; ///< global increasing request ID counter + + typedef CUtlVector< char * > StrVector; + StrVector m_downloadedMaps; ///< List of maps for which we have already tried to download assets. +}; + +//-------------------------------------------------------------------------------------------------------------- +static CDownloadManager s_DownloadManager; + +//-------------------------------------------------------------------------------------------------------------- +CDownloadManager::CDownloadManager() +{ + m_activeRequest = NULL; + m_lastPercent = 0; + m_totalRequests = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +CDownloadManager::~CDownloadManager() +{ + Reset(); + + for ( int i=0; i<m_downloadedMaps.Count(); ++i ) + { + delete[] m_downloadedMaps[i]; + } + m_downloadedMaps.RemoveAll(); +} + +//-------------------------------------------------------------------------------------------------------------- +RequestContext_t *CDownloadManager::NewRequestContext() +{ + return new RequestContext_t; +} + +//-------------------------------------------------------------------------------------------------------------- +void CDownloadManager::SetupURLPath( RequestContext_t *pRequestContext, const char *pURLPath ) +{ + V_strcpy( pRequestContext->urlPath, pRequestContext->gamePath ); +} +//-------------------------------------------------------------------------------------------------------------- +void CDownloadManager::SetupServerURL( RequestContext_t *pRequestContext ) +{ + Q_strncpy( pRequestContext->serverURL, cl.m_NetChannel->GetRemoteAddress().ToString(), BufferSize ); +} +//-------------------------------------------------------------------------------------------------------------- +bool CDownloadManager::HasMapBeenDownloadedFromServer( const char *serverMapName ) +{ + if ( !serverMapName ) + return false; + + for ( int i=0; i<m_downloadedMaps.Count(); ++i ) + { + const char *oldServerMapName = m_downloadedMaps[i]; + if ( oldServerMapName && !stricmp( serverMapName, oldServerMapName ) ) + { + return true; + } + } + return false; +} + +bool CDownloadManager::FileDenied( const char *filename, unsigned int requestID ) +{ + if ( !m_activeRequest ) + return false; + + if ( m_activeRequest->nRequestID != requestID ) + return false; + + if ( m_activeRequest->bAsHTTP ) + return false; + + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadErrorColor, "Error downloading %s\n", m_activeRequest->absLocalPath ); + } + + UpdateProgressBar(); + + // try to download the next file + m_completedRequests.AddToTail( m_activeRequest ); + m_activeRequest = NULL; + + return true; +} + +bool CDownloadManager::FileReceived( const char *filename, unsigned int requestID ) +{ + if ( !m_activeRequest ) + return false; + + if ( m_activeRequest->nRequestID != requestID ) + return false; + + if ( m_activeRequest->bAsHTTP ) + return false; + + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadCompleteColor, "Download finished!\n" ); + } + + UpdateProgressBar(); + + m_completedRequests.AddToTail( m_activeRequest ); + m_activeRequest = NULL; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +void CDownloadManager::MarkMapAsDownloadedFromServer( const char *serverMapName ) +{ + if ( !serverMapName ) + return; + + if ( HasMapBeenDownloadedFromServer( serverMapName ) ) + return; + + m_downloadedMaps.AddToTail( V_strdup( serverMapName ) ); + + + return; +} + +//-------------------------------------------------------------------------------------------------------------- +void CDownloadManager::QueueInternal( const char *pBaseURL, const char *pURLPath, const char *pGamePath, + bool bAsHttp, bool bCompressed ) +{ + // NOTE: Assumes valid game path (i.e. IsGamePathValidAndSafe() has been called already) + + ++m_totalRequests; + + // Initialize the download cache if necessary + if ( !TheDownloadCache ) + { + TheDownloadCache = new DownloadCache; + TheDownloadCache->Init(); + } + + // Create a new context and add queue it + RequestContext_t *rc = NewRequestContext(); + m_queuedRequests.AddToTail( rc ); + + rc->bIsBZ2 = bCompressed; + rc->bAsHTTP = bAsHttp; + rc->status = HTTP_CONNECTING; + + // Setup base path. We put it in the "download" search path, if they have set one + char szBasePath[ MAX_PATH ]; + if ( g_pFileSystem->GetSearchPath( k_szDownloadPathID, false, szBasePath, sizeof(szBasePath) ) > 0 ) + { + char *split = V_strstr( szBasePath, ";" ); + if ( split != NULL ) + { + Warning( "Multiple download search paths? Check gameinfo.txt" ); + *split = '\0'; + } + } + + // Otherwise, put it in the game dir + if ( szBasePath[0] == '\0' ) + V_strcpy_safe( szBasePath, com_gamedir ); + + // Setup game path + V_strcpy_safe( rc->gamePath, pGamePath ); + if ( bCompressed ) + { + V_strcat_safe( rc->gamePath, ".bz2" ); + } + Q_FixSlashes( rc->gamePath, '/' ); // only matters for debug prints, which are full URLS, so we want forward slashes + + // NOTE: Loose files on disk must always be lowercase! At least on Linux they HAVE to be, + // but we do the same thing on Windows to keep things consistent. + char szGamePathLower[MAX_PATH]; + V_strcpy_safe( szGamePathLower, rc->gamePath ); + V_strlower( szGamePathLower ); + + // Now set the full absolute path. Why does the file system not provide a convenient method to + // do stuff like this? + V_strcpy_safe( rc->absLocalPath, szBasePath ); + V_AppendSlash( rc->absLocalPath, sizeof(rc->absLocalPath) ); + V_strcat_safe( rc->absLocalPath, szGamePathLower ); + V_FixSlashes( rc->absLocalPath ); + + // Setup base URL if necessary + if ( bAsHttp ) + { + V_strcpy_safe( rc->baseURL, pBaseURL ); + V_StripTrailingSlash( rc->baseURL ); + V_strcat_safe( rc->baseURL, "/" ); + } + + // Call virtual methods for setting up additional context info + SetupURLPath( rc, pURLPath ); + SetupServerURL( rc ); + + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Queueing %s%s.\n", rc->baseURL, pGamePath ); + } + + // Invoke the callback if appropriate + if ( bAsHttp ) + { + OnHttpConnecting( rc ); + } +} + +void CDownloadManager::Queue( const char *baseURL, const char *urlPath, const char *gamePath ) +{ + if ( !CL_IsGamePathValidAndSafeForDownload( gamePath ) ) + return; + + // Don't download existing files + if ( g_pFileSystem->FileExists( gamePath ) ) + return; + +#ifndef _DEBUG + if ( sv.IsActive() ) + { + return; // don't try to download things for the local server (in case a map is missing sounds etc that + // aren't needed to play. + } +#endif + + // HTTP or NetChan? + bool bAsHTTP = baseURL && ( !Q_strnicmp( baseURL, "http://", 7 ) || !Q_strnicmp( baseURL, "https://", 8 ) ); + + // Queue up an HTTP download of the bzipped asset, in case it exists. + // When a bzipped download finishes, we'll uncompress the file to it's + // original destination, and the queued download of the uncompressed + // file will abort. + if ( bAsHTTP && ShouldAttemptCompressedFileDownload() && !g_pFileSystem->FileExists( va( "%s.bz2", gamePath ) ) ) + { + QueueInternal( baseURL, urlPath, gamePath, true, true ); + } + + // Queue up the straight, uncompressed version + QueueInternal( baseURL, urlPath, gamePath, bAsHTTP, false ); + + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Queueing %s%s.\n", baseURL, gamePath ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +void CDownloadManager::Reset() +{ + // ask the active request to bail + if ( m_activeRequest ) + { + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Aborting download of %s\n", m_activeRequest->gamePath ); + } + + if ( m_activeRequest->nBytesTotal && m_activeRequest->nBytesCurrent ) + { + // Persist partial data to cache + TheDownloadCache->PersistToCache( m_activeRequest ); + } + m_activeRequest->shouldStop = true; + m_completedRequests.AddToTail( m_activeRequest ); + m_activeRequest = NULL; + //TODO: StopLoadingProgressBar(); + } + + // clear out any queued requests + for ( int i=0; i<m_queuedRequests.Count(); ++i ) + { + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Discarding queued download of %s\n", m_queuedRequests[i]->gamePath ); + } + + delete m_queuedRequests[i]; + } + m_queuedRequests.RemoveAll(); + + if ( TheDownloadCache ) + { + delete TheDownloadCache; + TheDownloadCache = NULL; + } + + m_lastPercent = 0; + m_totalRequests = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +// Check download requests that have been completed to see if their threads have exited +void CDownloadManager::PruneCompletedRequests() +{ + for ( int i=m_completedRequests.Count()-1; i>=0; --i ) + { + if ( m_completedRequests[i]->threadDone || !m_completedRequests[i]->bAsHTTP ) + { + if ( m_completedRequests[i]->cacheData ) + { + delete[] m_completedRequests[i]->cacheData; + } + delete m_completedRequests[i]; + m_completedRequests.Remove( i ); + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +// Checks download status, and updates progress bar +void CDownloadManager::CheckActiveDownload() +{ + if ( !m_activeRequest ) + return; + + if ( !m_activeRequest->bAsHTTP ) + { + UpdateProgressBar(); + return; + } + + // check active request for completion / error / progress update + switch ( m_activeRequest->status ) + { + case HTTP_DONE: + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadCompleteColor, "Download finished!\n" ); + } + + UpdateProgressBar(); + OnHttpDone( m_activeRequest ); + if ( m_activeRequest->nBytesTotal ) + { + // Persist complete data to disk, and remove cache entry + //TODO: SetSecondaryProgressBarText( m_activeRequest->gamePath ); + TheDownloadCache->PersistToDisk( m_activeRequest ); + m_activeRequest->shouldStop = true; + m_completedRequests.AddToTail( m_activeRequest ); + m_activeRequest = NULL; + if ( !m_queuedRequests.Count() ) + { + //TODO: StopLoadingProgressBar(); + //TODO: Cbuf_AddText("retry\n"); + } + } + break; + case HTTP_ERROR: + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadErrorColor, "Error downloading %s%s\n", m_activeRequest->baseURL, m_activeRequest->gamePath ); + } + + UpdateProgressBar(); + + // try to download the next file + m_activeRequest->shouldStop = true; + m_completedRequests.AddToTail( m_activeRequest ); + OnHttpError( m_activeRequest ); + m_activeRequest = NULL; + if ( !m_queuedRequests.Count() ) + { + //TODO: StopLoadingProgressBar(); + //TODO: Cbuf_AddText("retry\n"); + } + break; + case HTTP_FETCH: + UpdateProgressBar(); + // Update progress bar + //TODO: SetSecondaryProgressBarText( m_activeRequest->gamePath ); + if ( m_activeRequest->nBytesTotal ) + { + int percent = ( m_activeRequest->nBytesCurrent * 100 / m_activeRequest->nBytesTotal ); + if ( percent != m_lastPercent ) + { + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Downloading %s%s: %3.3d%% - %d of %d bytes\n", + m_activeRequest->baseURL, m_activeRequest->gamePath, + percent, m_activeRequest->nBytesCurrent, m_activeRequest->nBytesTotal ); + } + + m_lastPercent = percent; + //TODO: SetSecondaryProgressBar( m_lastPercent * 0.01f ); + } + } + OnHttpFetch( m_activeRequest ); + break; + } +} + +//-------------------------------------------------------------------------------------------------------------- +// Starts a new download if there are queued requests +void CDownloadManager::StartNewDownload() +{ + if ( m_activeRequest || !m_queuedRequests.Count() ) + return; + + while ( !m_activeRequest && m_queuedRequests.Count() ) + { + // Remove one request from the queue and make it active + m_activeRequest = m_queuedRequests[0]; + m_queuedRequests.Remove( 0 ); + + if ( g_pFileSystem->FileExists( m_activeRequest->absLocalPath ) ) + { + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Skipping existing file %s%s.\n", m_activeRequest->baseURL, m_activeRequest->gamePath ); + } + + m_activeRequest->shouldStop = true; + m_activeRequest->threadDone = true; + m_completedRequests.AddToTail( m_activeRequest ); + m_activeRequest = NULL; + } + } + + if ( !m_activeRequest ) + return; + + if ( g_pFileSystem->FileExists( m_activeRequest->absLocalPath ) ) + { + m_activeRequest->shouldStop = true; + m_activeRequest->threadDone = true; + m_completedRequests.AddToTail( m_activeRequest ); + m_activeRequest = NULL; + return; // don't download existing files + } + + if ( m_activeRequest->bAsHTTP ) + { + // Check cache for partial match + TheDownloadCache->GetCachedData( m_activeRequest ); + + //TODO: ContinueLoadingProgressBar( "Http", m_totalRequests - m_queuedRequests.Count(), 0.0f ); + //TODO: SetLoadingProgressBarStatusText( "#GameUI_VerifyingAndDownloading" ); + //TODO: SetSecondaryProgressBarText( m_activeRequest->gamePath ); + //TODO: SetSecondaryProgressBar( 0.0f ); + UpdateProgressBar(); + + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Downloading %s%s.\n", m_activeRequest->baseURL, m_activeRequest->gamePath ); + } + + m_lastPercent = 0; + + // Start the thread + DWORD threadID; + VCRHook_CreateThread(NULL, 0, +#ifdef POSIX + (void *) +#endif + DownloadThread, m_activeRequest, 0, (unsigned long int *)&threadID ); + + ThreadDetach( ( ThreadHandle_t )threadID ); + } + else + { + UpdateProgressBar(); + + if ( download_debug.GetBool() ) + { + ConDColorMsg( DownloadColor, "Downloading %s.\n", m_activeRequest->gamePath ); + } + + m_lastPercent = 0; + + m_activeRequest->nRequestID = cl.m_NetChannel->RequestFile( m_activeRequest->gamePath ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +void CDownloadManager::UpdateProgressBar() +{ + if ( !m_activeRequest ) + { + return; + } + + wchar_t filenameBuf[MAX_OSPATH]; + float progress = 0.0f; + + if ( m_activeRequest->bAsHTTP ) + { + int overallPercent = (m_totalRequests - m_queuedRequests.Count() - 1) * 100 / m_totalRequests; + int filePercent = 0; + if ( m_activeRequest->nBytesTotal > 0 ) + { + filePercent = ( m_activeRequest->nBytesCurrent * 100 / m_activeRequest->nBytesTotal ); + } + + progress = (overallPercent + filePercent * 1.0f / m_totalRequests) * 0.01f; + } + else + { + int received, total; + cl.m_NetChannel->GetStreamProgress( FLOW_INCOMING, &received, &total ); + + progress = (float)(received)/(float)(total); + } + +#ifndef DEDICATED + _snwprintf( filenameBuf, 256, L"Downloading %hs", m_activeRequest->gamePath ); + EngineVGui()->UpdateCustomProgressBar( progress, filenameBuf ); +#endif +} + +//-------------------------------------------------------------------------------------------------------------- +// Monitors download thread, starts new downloads, and updates progress bar +bool CDownloadManager::Update() +{ + PruneCompletedRequests(); + CheckActiveDownload(); + StartNewDownload(); + + return m_activeRequest != NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +// Externally-visible function definitions +//-------------------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------------------- +bool CL_DownloadUpdate(void) +{ + return s_DownloadManager.Update(); +} + +//-------------------------------------------------------------------------------------------------------------- +void CL_HTTPStop_f(void) +{ + s_DownloadManager.Stop(); +} + +bool CL_FileReceived( const char *filename, unsigned int requestID ) +{ + return s_DownloadManager.FileReceived( filename, requestID ); +} + +bool CL_FileDenied( const char *filename, unsigned int requestID ) +{ + return s_DownloadManager.FileDenied( filename, requestID ); +} + +//-------------------------------------------------------------------------------------------------------------- +extern ConVar sv_downloadurl; +void CL_QueueDownload( const char *filename ) +{ + s_DownloadManager.Queue( sv_downloadurl.GetString(), NULL, filename ); +} + +//-------------------------------------------------------------------------------------------------------------- + +int CL_GetDownloadQueueSize(void) +{ + return s_DownloadManager.GetQueueSize(); +} + +//-------------------------------------------------------------------------------------------------------------- + +int CL_CanUseHTTPDownload(void) +{ + if ( sv_downloadurl.GetString()[0] ) + { + const char *serverMapName = va( "%s:%s", sv_downloadurl.GetString(), cl.m_szLevelFileName ); + return !s_DownloadManager.HasMapBeenDownloadedFromServer( serverMapName ); + } + return 0; +} + +//-------------------------------------------------------------------------------------------------------------- + +void CL_MarkMapAsUsingHTTPDownload(void) +{ + const char *serverMapName = va( "%s:%s", sv_downloadurl.GetString(), cl.m_szLevelFileName ); + s_DownloadManager.MarkMapAsDownloadedFromServer( serverMapName ); +} + +//-------------------------------------------------------------------------------------------------------------- + +bool CL_IsGamePathValidAndSafeForDownload( const char *pGamePath ) +{ + if ( !CNetChan::IsValidFileForTransfer( pGamePath ) ) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- + +class CDownloadSystem : public IDownloadSystem +{ +public: + virtual DWORD CreateDownloadThread( RequestContext_t *pContext ) + { + DWORD nThreadID; + VCRHook_CreateThread(NULL, 0, +#ifdef POSIX + (void*) +#endif + DownloadThread, pContext, 0, (unsigned long int *)&nThreadID ); + + ThreadDetach( ( ThreadHandle_t )nThreadID ); + return nThreadID; + } +}; + +//-------------------------------------------------------------------------------------------------------------- + +static CDownloadSystem s_DownloadSystem; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDownloadSystem, IDownloadSystem, INTERFACEVERSION_DOWNLOADSYSTEM, s_DownloadSystem ); + +//-------------------------------------------------------------------------------------------------------------- + |