source-engine/engine/download.cpp

1094 lines
32 KiB
C++
Raw Normal View History

2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
//--------------------------------------------------------------------------------------------------------------
// download.cpp
//
// Implementation file for optional HTTP asset downloading
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 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
uintp threadID;
2020-04-23 00:56:21 +08:00
VCRHook_CreateThread(NULL, 0,
#ifdef POSIX
(void *)
#endif
2022-02-23 19:50:30 +08:00
DownloadThread, m_activeRequest, 0, &threadID );
2020-04-23 00:56:21 +08:00
ReleaseThreadHandle( ( ThreadHandle_t )threadID );
2020-04-23 00:56:21 +08:00
}
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:
2022-02-23 19:50:30 +08:00
virtual uintp CreateDownloadThread( RequestContext_t *pContext )
2020-04-23 00:56:21 +08:00
{
uintp nThreadID;
2020-04-23 00:56:21 +08:00
VCRHook_CreateThread(NULL, 0,
#ifdef POSIX
(void*)
#endif
2022-02-23 19:50:30 +08:00
DownloadThread, pContext, 0, (uintp *)&nThreadID );
2020-04-23 00:56:21 +08:00
ReleaseThreadHandle( ( ThreadHandle_t )nThreadID );
2020-04-23 00:56:21 +08:00
return nThreadID;
}
};
//--------------------------------------------------------------------------------------------------------------
static CDownloadSystem s_DownloadSystem;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDownloadSystem, IDownloadSystem, INTERFACEVERSION_DOWNLOADSYSTEM, s_DownloadSystem );
//--------------------------------------------------------------------------------------------------------------