|
|
//========== Copyright Valve Corporation, All rights reserved. ========
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "dedicated_server_ugc_manager.h"
#include "steam/isteamhttp.h"
#include "ugc_utils.h"
#include "tier2/fileutils.h"
#include "gametypes.h"
// TODO: can we swap this out based on steam universe?
const char* g_szAuthKeyFilename = "webapi_authkey.txt"; const char* g_szCollectionCacheFileName = "ugc_collection_cache.txt";
const char* g_szSubscribedFilesList = "subscribed_file_ids.txt"; const char* g_szSubscribedCollectionsList = "subscribed_collection_ids.txt";
// Subdir relative to game dir to store workshop maps in
const char* g_szWorkshopMapBasePath = "maps/workshop";
const char* GetApiBaseUrl( void ) { return "https://api.steampowered.com"; /*
if ( steamapicontext && steamapicontext->SteamUtils() ) { if ( steamapicontext->SteamUtils()->GetConnectedUniverse() == k_EUniverseBeta ) return "https://api-beta.steampowered.com"; else if ( steamapicontext->SteamUtils()->GetConnectedUniverse() == k_EUniversePublic ) return "https://api.steampowered.com"; }
Assert( 0 ); return ""; */ }
ConVar sv_debug_ugc_downloads( "sv_debug_ugc_downloads", "0", FCVAR_RELEASE ); ConVar sv_broadcast_ugc_downloads( "sv_broadcast_ugc_downloads", "0", FCVAR_RELEASE ); ConVar sv_broadcast_ugc_download_progress_interval( "sv_broadcast_ugc_download_progress_interval", "8", FCVAR_RELEASE ); ConVar sv_ugc_manager_max_new_file_check_interval_secs( "sv_ugc_manager_max_new_file_check_interval_secs", "1000", FCVAR_RELEASE ); ConVar sv_remove_old_ugc_downloads( "sv_remove_old_ugc_downloads", "1", FCVAR_RELEASE ); ConVar sv_test_steam_connection_failure( "sv_test_steam_connection_failure", "0" );
CDedicatedServerWorkshopManager g_DedicatedServerWorkshopManager; CDedicatedServerWorkshopManager& DedicatedServerWorkshop( void ) { return g_DedicatedServerWorkshopManager; }
CON_COMMAND( workshop_start_map, "Sets the first map to load once a workshop collection been hosted. Takes the file id of desired start map as a parameter." ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return;
if ( args.ArgC() != 2 ) { Msg( "Usage: workshop_start_map <fileid>\n"); return; }
PublishedFileId_t id = (PublishedFileId_t)V_atoui64( args[1] ); if ( id == 0 ) { Msg( "Invalid file id.\n"); return; }
DedicatedServerWorkshop().SetTargetStartMap( id ); }
CON_COMMAND( host_workshop_map, "Get the latest version of the map and host it on this server." ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return;
if ( args.ArgC() != 2 ) { Msg( "Usage: host_workshop_map <fileid>\n"); return; }
PublishedFileId_t id = (PublishedFileId_t)V_atoui64( args[1] ); if ( id == 0 ) { Msg( "Invalid file id.\n"); return; }
// HACK: Need to load a map for steam server api to be available and download maps... peeling out the init code
// would be better, but loading dust works for now.
if ( !steamgameserverapicontext || !steamgameserverapicontext->SteamHTTP() ) engine->ServerCommand( CFmtStr( "map de_dust server_is_unavailable\n" ).Access() );
DedicatedServerWorkshop().HostWorkshopMap( id ); }
CON_COMMAND( host_workshop_collection, "Get the latest version of maps in a workshop collection and host them as a maplist." ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return;
if ( args.ArgC() != 2 ) { Msg( "Usage: host_workshop_collection <fileid>\n"); return; }
PublishedFileId_t id = (PublishedFileId_t)V_atoui64( args[1] ); if ( id == 0 ) { Msg( "Invalid file id.\n"); return; }
// HACK: Need to load a map for steam server api to be available and download maps... peeling out the init code
// would be better, but loading dust works for now.
if ( !steamgameserverapicontext || !steamgameserverapicontext->SteamHTTP() ) engine->ServerCommand( CFmtStr( "map de_dust server_is_unavailable\n" ).Access() );
DedicatedServerWorkshop().HostWorkshopMapCollection( id ); }
bool DedicatedServerUGCFileInfo_t::BuildFromKV( KeyValues *pPublishedFileDetails ) { m_bIsValid = false; m_result = (EResult)pPublishedFileDetails->GetInt( "result", -1 );
// Parse file id first, we should get this even on failures.
fileId = pPublishedFileDetails->GetUint64( "publishedfileid", 0ll ); if ( !fileId ) return false;
if ( m_result != k_EResultOK ) return false;
int appId = pPublishedFileDetails->GetInt( "consumer_appid", 0 ); if ( appId != engine->GetAppID() ) return false;
if ( pPublishedFileDetails->GetInt( "banned", 0 ) != 0 ) return false;
contentHandle = pPublishedFileDetails->GetUint64( "hcontent_file", 0ll ); if ( !contentHandle ) return false; const char* szUrl = pPublishedFileDetails->GetString( "file_url", NULL ); if ( !szUrl ) return false; V_strcpy_safe( m_szUrl, szUrl );
const char* szName = V_UnqualifiedFileName( pPublishedFileDetails->GetString( "filename", NULL ) ); if ( !szName ) return false; V_strcpy_safe( m_szFileName, szName );
m_unFileSizeInBytes = pPublishedFileDetails->GetInt( "file_size", 0 ); if ( !m_unFileSizeInBytes ) return false; const char* szTitle = pPublishedFileDetails->GetString( "title", NULL ); if ( !szTitle ) return false; V_strcpy_safe( m_szTitle, szTitle );
m_unTimeLastUpdated = pPublishedFileDetails->GetInt( "time_updated", 0 ); if ( !m_unTimeLastUpdated ) return false;
// Assuming we're downloading maps here...
V_snprintf( m_szFilePath, ARRAYSIZE(m_szFilePath), "%s/%llu/%s", g_szWorkshopMapBasePath, fileId, szName );
// TODO tags
m_bIsValid = true; m_dblPlatFloatTimeReceived = Plat_FloatTime(); return true; }
void ParseFileIds( const char* szFileName, CUtlVector<PublishedFileId_t>& outVec ) { if ( filesystem->FileExists( szFileName ) ) { int fileSize; char* szFileBuf = (char*)UTIL_LoadFileForMe( szFileName, &fileSize ); if ( szFileBuf && fileSize > 0 ) { CUtlStringList fileIdList; V_SplitString( szFileBuf, "\n", fileIdList ); for ( int i = 0; i < fileIdList.Count(); ++i ) { PublishedFileId_t id = V_atoui64( fileIdList[i] ); if ( !outVec.HasElement( id ) ) outVec.AddToTail( id );
if ( sv_debug_ugc_downloads.GetBool() ) Msg( "CDedicatedServerWorkshopManager::Init: Subscribing to file id %llu\n", id ); } delete[] szFileBuf; } } }
bool CDedicatedServerWorkshopManager::Init( void ) { m_UGCFileInfos.SetLessFunc( DefLessFunc( PublishedFileId_t ) ); m_mapWorkshopIdsToMapNames.SetLessFunc( DefLessFunc( PublishedFileId_t) ); m_mapPreviousCollectionQueryCache.SetLessFunc( DefLessFunc( PublishedFileId_t) ); m_fTimeLastVersionCheck = 0; GetNewestSubscribedFiles();
// HACK: So if we load a map before we hear back from steam about what files we have subscribed,
// we won't submit any workshop map IDs to matchmaking. Scan for any maps in the workshop subdirectory
// and use any bsps found as available maps.
CUtlVector<CUtlString> outList; RecursiveFindFilesMatchingName( &outList, g_szWorkshopMapBasePath, "*.bsp", "MOD" ); FOR_EACH_VEC( outList, i ) { CUtlString &curMap = outList[i]; PublishedFileId_t id = GetUGCMapPublishedFileID( curMap.Access() ); if ( id != 0 ) { NoteWorkshopMapOnDisk( id, curMap.Access() ); } } m_bHostedCollectionUpdatePending = false ; m_unTargetStartMap = 0;
if ( g_pFullFileSystem->FileExists( g_szCollectionCacheFileName, "MOD" ) ) { KeyValues* pCollectionCacheKV = new KeyValues(""); KeyValues::AutoDelete autodelete( pCollectionCacheKV ); pCollectionCacheKV->LoadFromFile( g_pFullFileSystem, g_szCollectionCacheFileName, "MOD" ); for ( KeyValues *pDetails = pCollectionCacheKV->GetFirstSubKey(); pDetails != NULL; pDetails = pDetails->GetNextKey() ) { PublishedFileId_t collectionId = pDetails->GetUint64( "publishedfileid", 0 ); if ( collectionId != 0 ) { m_mapPreviousCollectionQueryCache.Insert( collectionId, pDetails->MakeCopy() ); } } }
return true; }
void CDedicatedServerWorkshopManager::LevelInitPreEntity( void ) { // reset these every level change.
m_hackCurrentMapInfoCheck = 0; m_bCurrentLevelNeedsUpdate = false; m_bHostedCollectionUpdatePending = false; }
void CDedicatedServerWorkshopManager::CheckIfCurrentLevelNeedsUpdate( void ) { m_bCurrentLevelNeedsUpdate = false; PublishedFileId_t id = GetUGCMapPublishedFileID( gpGlobals->mapname.ToCStr() ); if ( id != 0 ) { m_hackCurrentMapInfoCheck = id; if ( !m_FileInfoQueries.HasElement( id ) ) m_FileInfoQueries.AddToTail( id ); } }
CON_COMMAND_F( ds_get_newest_subscribed_files, "Re-reads web api auth key and subscribed file lists from disk and downloads the latest updates of those files from steam", FCVAR_RELEASE ) { g_DedicatedServerWorkshopManager.GetNewestSubscribedFiles(); }
void CDedicatedServerWorkshopManager::GetNewestSubscribedFiles( void ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg("CDedicatedServerWorkshopManager::GetNewestSubscribedFiles\n");
if ( engine->IsDedicatedServer() ) { Q_memset( m_szWebAPIAuthKey, 0, ARRAYSIZE( m_szWebAPIAuthKey ) ); const char *szAuthKey = CommandLine()->ParmValue( "-authkey", "" ); if ( !StringIsEmpty( szAuthKey ) ) { V_strcpy_safe( m_szWebAPIAuthKey, szAuthKey ); } else if ( filesystem->FileExists( g_szAuthKeyFilename, "MOD" ) ) { int nLength; szAuthKey = (const char *)UTIL_LoadFileForMe( g_szAuthKeyFilename, &nLength ); if ( szAuthKey != NULL ) { if ( !StringIsEmpty( szAuthKey ) ) { V_strcpy_safe( m_szWebAPIAuthKey, szAuthKey ); int len = strlen(m_szWebAPIAuthKey); while ( len > 0 && V_isspace(m_szWebAPIAuthKey[len-1]) ) { m_szWebAPIAuthKey[len-1] = 0; len--; } if (len>0) { Msg( "Loaded authkey from %s: %s\n", g_szAuthKeyFilename, m_szWebAPIAuthKey ); } } UTIL_FreeFile( (byte *)szAuthKey ); szAuthKey = NULL; } if ( StringIsEmpty(m_szWebAPIAuthKey) ) { Msg( "Auth key file %s not valid\n", g_szAuthKeyFilename ); } }
if ( ! StringIsEmpty(m_szWebAPIAuthKey) ) { m_bFoundAuthKey = true; if ( sv_debug_ugc_downloads.GetBool() ) Msg( "CDedicatedServerWorkshopManager::Init: Using auth key [%s]\n", m_szWebAPIAuthKey ); } else { m_bFoundAuthKey = false; Msg( "No web api auth key specified - workshop downloads will be disabled.\n" ); }
if ( m_bFoundAuthKey ) { //TODO: protect double adds?
ParseFileIds( g_szSubscribedFilesList, m_FileInfoQueries ); m_vecMapsBeingUpdated.AddVectorToTail( m_FileInfoQueries ); ParseFileIds( g_szSubscribedCollectionsList, m_CollectionInfoQueries );
// If we're hosting a workshop map collection, get the latest version of those maps
PublishedFileId_t id = V_atoui64( gpGlobals->mapGroupName.ToCStr() ); if ( g_pGameTypes->IsWorkshopMapGroup( gpGlobals->mapGroupName.ToCStr() ) && id != 0 ) { // Clumsy special case for single maps: If there's one entry and it's ID matches the collection name, it's really just a map and not a collection
const CUtlStringList * pMapList = g_pGameTypes->GetMapGroupMapList( gpGlobals->mapGroupName.ToCStr() ); if ( pMapList->Count() == 1 && GetUGCMapPublishedFileID( (*pMapList)[0] ) == id ) { UpdateFile( id ); } else { m_CollectionInfoQueries.AddToTail( id ); } }
m_fTimeLastVersionCheck = Plat_FloatTime(); } } }
void CDedicatedServerWorkshopManager::Shutdown( void ) { KeyValues* pOutKV = new KeyValues( "CollectionInfoCache" ); KeyValues::AutoDelete autodelete( pOutKV ); FOR_EACH_MAP( m_mapPreviousCollectionQueryCache, i ) { pOutKV->AddSubKey( m_mapPreviousCollectionQueryCache[i]->MakeCopy() ); m_mapPreviousCollectionQueryCache[i]->deleteThis(); } pOutKV->SaveToFile( g_pFullFileSystem, g_szCollectionCacheFileName, "MOD" );
Cleanup(); }
void CDedicatedServerWorkshopManager::Cleanup( void ) { FOR_EACH_VEC_BACK( m_PendingFileDownloads, i ) { delete m_PendingFileDownloads[i]; m_PendingFileDownloads.Remove( i ); } m_UGCFileInfos.PurgeAndDeleteElements(); m_FileInfoQueries.RemoveAll(); m_CollectionInfoQueries.RemoveAll(); m_vecWorkshopMapList.RemoveAll(); m_mapWorkshopIdsToMapNames.RemoveAll(); m_vecMapsBeingUpdated.RemoveAll(); m_bFoundAuthKey = false; m_desiredHostCollection = 0; m_bHostedCollectionUpdatePending = false;
if ( m_pMapGroupBuilder ) { delete m_pMapGroupBuilder; m_pMapGroupBuilder = NULL; } }
void CDedicatedServerWorkshopManager::Update( void ) { if ( !m_bFoundAuthKey ) return;
if ( steamgameserverapicontext == NULL ) return;
UpdatePublishedFileInfoRequests(); UpdateUGCDownloadRequests();
if ( m_pMapGroupBuilder ) { if ( m_pMapGroupBuilder->IsFinished() ) { m_pMapGroupBuilder->CreateOrUpdateMapGroup();
if ( m_desiredHostCollection != 0 && m_pMapGroupBuilder->GetFirstMap() != NULL ) { // Set the map group and changelevel if this was our target hosting map group
const char* szStartMap = m_unTargetStartMap ? m_pMapGroupBuilder->GetMapMatchingId( m_unTargetStartMap ) : m_pMapGroupBuilder->GetFirstMap(); engine->ServerCommand( CFmtStr( "mapgroup %llu;map %s\n", m_pMapGroupBuilder->GetId(), szStartMap ).Access() ); m_unTargetStartMap = 0; }
delete m_pMapGroupBuilder; m_pMapGroupBuilder = NULL; m_desiredHostCollection = 0; } } }
bool CDedicatedServerWorkshopManager::ShouldUpdateCollection( PublishedFileId_t id, const CUtlVector<PublishedFileId_t>& vecMaps ) { // Special case for hosted collection updates: Don't re-query all the file infos if our collection contents hasn't changed.
// For large collections in locations with high ping to steam, getting all that file info takes too long and hangs up level changes.
const char *szMapGroup = gpGlobals->mapGroupName.ToCStr(); bool bUpdateCollectionFiles = true; if ( m_bHostedCollectionUpdatePending && g_pGameTypes->IsWorkshopMapGroup( szMapGroup ) ) { PublishedFileId_t curHostedCollectionID = V_atoui64( szMapGroup ); Assert ( curHostedCollectionID != 0 ); // map groups hosted by dedicated servers should always have a uint64 name
if ( curHostedCollectionID == id ) { const CUtlStringList &maplist = *g_pGameTypes->GetMapGroupMapList( szMapGroup ); // NOTE: this gives false positives if the collection contains invalid ids (eg, removed from workshop, etc)
// because bad items in the list won't end up in the final map group, causing the count mismatch...
bool bChanged = maplist.Count() != vecMaps.Count(); if ( !bChanged ) { // If count matches, make sure the contents are the same. If so, we can skip updating the collection
FOR_EACH_VEC( maplist, i ) { PublishedFileId_t id = GetUGCMapPublishedFileID( maplist[i] ); if ( vecMaps.Find( id ) == -1 ) { bChanged = true; break; } } }
bUpdateCollectionFiles = bChanged; } // Make sure we mark our collection as no longer updating
if ( ( id == m_desiredHostCollection ) || ( id == curHostedCollectionID ) ) { m_bHostedCollectionUpdatePending = false; }
}
return bUpdateCollectionFiles; }
void CDedicatedServerWorkshopManager::UpdatePublishedFileInfoRequests( void ) { // Process finished queries
FOR_EACH_VEC_BACK( m_PendingFileInfoRequests, i ) { CPublishedFileInfoHTTPRequest* pCurRequest = m_PendingFileInfoRequests[i]; if( !pCurRequest->IsFinished() ) // still waiting on a response
continue;
if ( pCurRequest->GetLastHTTPResult() != k_EHTTPStatusCode200OK || sv_test_steam_connection_failure.GetBool() ) { // Handle http errors, retries
// Remove failed map ids from the pending list
if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Failed to get file info information from steam, HTTP status: %d\n. Missing info for file ids: ", pCurRequest->GetLastHTTPResult() ); FOR_EACH_VEC( pCurRequest->GetItemsQueried(), i ) { PublishedFileId_t id = pCurRequest->GetItemsQueried()[i]; OnFileInfoRequestFailed( id ); if ( sv_debug_ugc_downloads.GetBool() ) Msg( "%llu ", id ); } if ( sv_debug_ugc_downloads.GetBool() ) Msg( "\n" ); } else { FOR_EACH_VEC( pCurRequest->GetFileInfoList(), j ) { const DedicatedServerUGCFileInfo_t* pCurInfo = pCurRequest->GetFileInfoList()[j];
OnFileInfoReceived( pCurInfo );
if ( pCurInfo->m_result == k_EResultOK && pCurInfo->m_bIsValid ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "CDedicatedServerWorkshopManager: received file details for id %llu: '%s'.\n", pCurInfo->fileId, pCurInfo->m_szTitle ? pCurInfo->m_szTitle : "<no title>" );
RemoveFileInfo( pCurInfo->fileId ); // Clear any existing entry and cancel any pending download.
DedicatedServerUGCFileInfo_t* pNewFileInfo = new DedicatedServerUGCFileInfo_t; V_memcpy( (void*)pNewFileInfo, (void*)pCurInfo, sizeof ( DedicatedServerUGCFileInfo_t ) ); m_UGCFileInfos.Insert( pNewFileInfo->fileId, pNewFileInfo );
// Skip downloading if this is an 'info only' id.
if ( m_hackCurrentMapInfoCheck == pNewFileInfo->fileId ) { m_hackCurrentMapInfoCheck = 0; continue; }
if ( !IsFileLatestVersion( pNewFileInfo ) ) { QueueDownloadFile( pNewFileInfo ); } else { OnFileDownloaded( pNewFileInfo ); if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Skipping download for file id %llu:'%s' - version on disk is latest.\n", pNewFileInfo->fileId, pNewFileInfo->m_szTitle ? pNewFileInfo->m_szTitle : "<no title>" ); } } else { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Failed to parse file details KV for id %llu. Result enum: %d\n", pCurInfo->fileId, pCurInfo->m_result );
if ( pCurInfo->m_result == k_EResultFileNotFound ) Msg( "File id %llu not found. Probably removed from workshop\n", pCurInfo->fileId ); } } }
delete pCurRequest; // request dealt with
m_PendingFileInfoRequests.Remove( i ); }
FOR_EACH_VEC_BACK( m_PendingCollectionInfoRequests, i ) { CCollectionInfoHTTPRequest *pCurRequest = m_PendingCollectionInfoRequests[i];
if ( !pCurRequest->IsFinished() ) continue;
if ( pCurRequest->GetLastHTTPResult() != k_EHTTPStatusCode200OK || sv_test_steam_connection_failure.GetBool() ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Failed to get file info information from steam, HTTP status: %d\n. Missing info for collection ids: ", pCurRequest->GetLastHTTPResult() ); FOR_EACH_VEC( pCurRequest->GetItemsQueried(), i ) { PublishedFileId_t id = pCurRequest->GetItemsQueried()[i]; OnCollectionInfoRequestFailed( id ); if ( sv_debug_ugc_downloads.GetBool() ) Msg( "%llu ", id );
if ( id == m_desiredHostCollection ) { int idx = m_mapPreviousCollectionQueryCache.Find( id ); if ( idx != m_mapPreviousCollectionQueryCache.InvalidIndex() ) { ParseCollectionInfo( m_mapPreviousCollectionQueryCache[idx] ); } else { m_bHostedCollectionUpdatePending = false; // failed to get info on host collection, and we didn't have it in our cache
} } } if ( sv_debug_ugc_downloads.GetBool() ) Msg( "\n" ); } else { KeyValues* pCollectionDetails = pCurRequest->GetResponseKV(); for ( KeyValues *pDetails = pCollectionDetails->GetFirstSubKey(); pDetails != NULL; pDetails = pDetails->GetNextKey() ) { PublishedFileId_t collectionId = ParseCollectionInfo( pDetails ); if ( collectionId != 0 ) { // Save previously queried collection infos to disk in case we lose connection to steam
int idx = m_mapPreviousCollectionQueryCache.Find( collectionId ); if ( idx == m_mapPreviousCollectionQueryCache.InvalidIndex() ) idx = m_mapPreviousCollectionQueryCache.Insert( collectionId ); else m_mapPreviousCollectionQueryCache[idx]->deleteThis();
m_mapPreviousCollectionQueryCache[idx] = pDetails->MakeCopy(); }
} }
delete pCurRequest; m_PendingCollectionInfoRequests.Remove( i ); }
if ( m_FileInfoQueries.Count() > 0 ) { CPublishedFileInfoHTTPRequest *pRequest = new CPublishedFileInfoHTTPRequest( m_FileInfoQueries ); pRequest->CreateHTTPRequest( m_szWebAPIAuthKey ); m_PendingFileInfoRequests.AddToTail( pRequest ); m_FileInfoQueries.RemoveAll(); }
if ( m_CollectionInfoQueries.Count() > 0 ) { CCollectionInfoHTTPRequest *pRequest = new CCollectionInfoHTTPRequest( m_CollectionInfoQueries ); pRequest->CreateHTTPRequest( m_szWebAPIAuthKey ); m_PendingCollectionInfoRequests.AddToTail( pRequest ); m_CollectionInfoQueries.RemoveAll(); } }
void CDedicatedServerWorkshopManager::UpdateUGCDownloadRequests( void ) { if ( m_PendingFileDownloads.Count() ) { // TODO: Handle timeouts/errors?
m_PendingFileDownloads[0]->Update(); if ( m_PendingFileDownloads[0]->IsFinished() ) { OnFileDownloaded( m_PendingFileDownloads[0]->GetFileInfo() ); delete m_PendingFileDownloads[0]; m_PendingFileDownloads.Remove( 0 ); } } /*
BUG/TODO: Downloading lots of files at the same time runs out of memory in GetHTTPResponseBodyData growing a buffer... Only downloading one at a time for now. FOR_EACH_VEC_BACK( m_PendingFileDownloads, i ) { m_PendingFileDownloads[i]->Update(); if ( m_PendingFileDownloads[i]->IsFinished() ) { delete m_PendingFileDownloads[i]; m_PendingFileDownloads.Remove( i ); } } */ }
void CDedicatedServerWorkshopManager::QueueDownloadFile( const DedicatedServerUGCFileInfo_t *pFileInfo ) { CStreamingUGCDownloader *pDownloader = new CStreamingUGCDownloader; pDownloader->StartFileDownload( pFileInfo, 1024*1024 ); m_PendingFileDownloads.AddToTail( pDownloader ); }
bool CDedicatedServerWorkshopManager::IsFileLatestVersion( const DedicatedServerUGCFileInfo_t* ugcInfo ) { // never try to update an official map, they're shipped with the depot
if ( UGCUtil_IsOfficialMap( ugcInfo->fileId ) ) return true;
if ( g_pFullFileSystem->FileExists( ugcInfo->m_szFilePath ) ) { // mtime needs to match the time last updated exactly, as we slam the file time when we download
// so an earlier time is out of date and a later time may be modified due to file copying.
uint32 fileTime = (uint32)g_pFullFileSystem->GetFileTime( ugcInfo->m_szFilePath ); if ( ugcInfo->m_unTimeLastUpdated == fileTime ) return true; } return false; }
void CDedicatedServerWorkshopManager::RemoveFileInfo( PublishedFileId_t id ) { unsigned short idx = m_UGCFileInfos.Find( id ); if ( idx != m_UGCFileInfos.InvalidIndex() ) { // Cancel any pending download
FOR_EACH_VEC_BACK( m_PendingFileDownloads, i ) { if ( m_PendingFileDownloads[i]->GetPublishedFileId() == id ) { delete m_PendingFileDownloads[i]; m_PendingFileDownloads.Remove( i ); } } delete m_UGCFileInfos[idx]; m_UGCFileInfos.RemoveAt( idx ); } }
bool CDedicatedServerWorkshopManager::GetMapsMatchingName( const char* szMapName, CUtlVector<const DedicatedServerUGCFileInfo_t*>& outVec ) const { bool bFoundAny = false; FOR_EACH_MAP( m_UGCFileInfos, i ) { const DedicatedServerUGCFileInfo_t *pInfo = m_UGCFileInfos[i]; // compare just map name (ignore extension and paths passed in)
char szBaseQueryMapName[MAX_PATH], szBaseUGCMapName[MAX_PATH]; V_FileBase( szMapName, szBaseQueryMapName, ARRAYSIZE( szBaseQueryMapName ) ); V_FileBase( pInfo->m_szFileName, szBaseUGCMapName, ARRAYSIZE( szBaseUGCMapName ) );
if ( V_strcmp( szBaseQueryMapName, szBaseUGCMapName ) == 0 ) { outVec.AddToTail( pInfo ); bFoundAny = true; } }
return bFoundAny; }
// Get the maps for which we downloaded UGC information successfully
void CDedicatedServerWorkshopManager::GetWorkshopMasWithValidUgcInformation( CUtlVector<const DedicatedServerUGCFileInfo_t *>& outVec ) const { FOR_EACH_MAP( m_UGCFileInfos, i ) { const DedicatedServerUGCFileInfo_t *pInfo = m_UGCFileInfos[i]; if ( !pInfo ) continue;
outVec.AddToTail( pInfo ); } }
// HACK: Using the map's directory to get the published file id...
PublishedFileId_t CDedicatedServerWorkshopManager::GetUGCMapPublishedFileID( const char* szPathToUGCMap ) const { char tmp[MAX_PATH]; V_strcpy_safe( tmp, szPathToUGCMap ); V_FixSlashes( tmp, '/' ); // internal path strings use forward slashes, make sure we compare like that.
//if ( !V_strncmp( tmp, g_szWorkshopMapBasePath, strlen( g_szWorkshopMapBasePath ) ) )
if ( V_strstr( tmp, "workshop/" ) ) { V_StripFilename(tmp); V_StripTrailingSlash(tmp); const char* szDirName = V_GetFileName(tmp); return (PublishedFileId_t)V_atoui64(szDirName); } /*
char szBspNoExtension[MAX_PATH]; V_strcpy_safe( szBspNoExtension, szPathToUGCMap ); V_StripExtension( szBspNoExtension, szBspNoExtension, sizeof( szBspNoExtension ) );
char szElemPathNoExt[MAX_PATH]; FOR_EACH_MAP( m_UGCFileInfos, i ) { DedicatedServerUGCFileInfo_t* pElem = m_UGCFileInfos.Element(i); if ( !V_stricmp( szPathToUGCMap, szBSPPath ) ) { return m_UGCFileInfos.Key(i); } } */ return 0; }
const CUtlVector< PublishedFileId_t > & CDedicatedServerWorkshopManager::GetWorkshopMapList( void ) const { return m_vecWorkshopMapList; }
// Records all workshop maps we have on disk (may or may not be up to date).
void CDedicatedServerWorkshopManager::NoteWorkshopMapOnDisk( PublishedFileId_t id, const char* szPath ) { if ( m_vecWorkshopMapList.Find( id ) == m_vecWorkshopMapList.InvalidIndex() ) { m_vecWorkshopMapList.AddToTail( id ); int idx = m_mapWorkshopIdsToMapNames.Insert( id ); m_mapWorkshopIdsToMapNames[idx].Set( szPath ); V_FixSlashes( m_mapWorkshopIdsToMapNames[idx].Get(), '/' ); // Always refer to map paths with forward slashes internally for consistancy.
} }
void CDedicatedServerWorkshopManager::HostWorkshopMap( PublishedFileId_t id ) { UpdateFile( id ); m_desiredHostCollection = id; }
void CDedicatedServerWorkshopManager::HostWorkshopMapCollection( PublishedFileId_t id ) { if ( m_bFoundAuthKey == false ) { Warning( "host_workshop_collection: Web API auth key not found!\n" ); }
if ( !m_CollectionInfoQueries.HasElement( id ) ) m_CollectionInfoQueries.AddToTail( id );
m_desiredHostCollection = id; }
// Called each time we get ugc file metadata from steam. Assumes this will get called before OnFileDownloaded.
void CDedicatedServerWorkshopManager::OnFileInfoReceived( const DedicatedServerUGCFileInfo_t *pInfo ) { if ( pInfo->m_result != k_EResultOK ) { if ( m_pMapGroupBuilder ) { m_pMapGroupBuilder->RemoveRequiredMap( pInfo->fileId ); } m_vecMapsBeingUpdated.FindAndFastRemove( pInfo->fileId ); }
// Host single maps as a collection of one
if ( pInfo->fileId == m_desiredHostCollection ) { if ( m_pMapGroupBuilder ) delete m_pMapGroupBuilder;
CUtlVector< PublishedFileId_t > vecMapFile; vecMapFile.AddToTail( pInfo->fileId ); m_pMapGroupBuilder = new CWorkshopMapGroupBuilder( pInfo->fileId, vecMapFile ); }
if( pInfo->fileId == m_hackCurrentMapInfoCheck ) { m_bCurrentLevelNeedsUpdate = !IsFileLatestVersion( pInfo ); } }
void CDedicatedServerWorkshopManager::OnFileInfoRequestFailed( PublishedFileId_t id ) { m_vecMapsBeingUpdated.FindAndRemove( id ); // no longer being updated
if ( m_pMapGroupBuilder ) { m_pMapGroupBuilder->RemoveRequiredMap( id ); } }
void CDedicatedServerWorkshopManager::OnCollectionInfoReceived( PublishedFileId_t collectionId, const CUtlVector< PublishedFileId_t > & vecCollectionItems ) { if ( vecCollectionItems.Count() > 0 ) { PublishedFileId_t curHostedCollection = 0; if( g_pGameTypes->IsWorkshopMapGroup( gpGlobals->mapGroupName.ToCStr() ) ) { curHostedCollection = V_atoui64( gpGlobals->mapGroupName.ToCStr() ); }
// Make/refresh a mapgroup if it's the one we want to/are hosting.
if ( collectionId == m_desiredHostCollection || collectionId == curHostedCollection ) { if ( m_pMapGroupBuilder ) delete m_pMapGroupBuilder;
m_pMapGroupBuilder = new CWorkshopMapGroupBuilder( collectionId, vecCollectionItems ); m_bHostedCollectionUpdatePending = false; } } }
void CDedicatedServerWorkshopManager::OnCollectionInfoRequestFailed( PublishedFileId_t id ) { }
void CDedicatedServerWorkshopManager::OnFileDownloaded( const DedicatedServerUGCFileInfo_t *pInfo ) { if ( m_pMapGroupBuilder ) { m_pMapGroupBuilder->OnMapDownloaded( pInfo ); }
m_vecMapsBeingUpdated.FindAndFastRemove( pInfo->fileId ); NoteWorkshopMapOnDisk( pInfo->fileId, pInfo->m_szFilePath ); }
bool CDedicatedServerWorkshopManager::HasPendingMapDownloads( void ) const { if ( !steamgameserverapicontext || !steamgameserverapicontext->SteamHTTP() || !steamgameserverapicontext->SteamGameServer() || !steamgameserverapicontext->SteamGameServer()->BLoggedOn() || (engine->GetGameServerSteamID() && engine->GetGameServerSteamID()->GetEAccountType() == k_EAccountTypeInvalid) ) return false; // TODO: Timeouts, errors
// If we have maps in the list waiting on info/downloads, or if we are trying to get the lastest info on our hosted collection
return ( m_vecMapsBeingUpdated.Count() != 0 || m_bHostedCollectionUpdatePending ); }
void CDedicatedServerWorkshopManager::UpdateFile( PublishedFileId_t id ) { if ( !m_FileInfoQueries.HasElement( id ) ) m_FileInfoQueries.AddToTail( id );
if ( !m_vecMapsBeingUpdated.HasElement( id ) ) m_vecMapsBeingUpdated.AddToTail( id ); }
void CDedicatedServerWorkshopManager::UpdateFiles( const CUtlVector<PublishedFileId_t>& vecFileIDs ) { FOR_EACH_VEC( vecFileIDs, i ) { UpdateFile( vecFileIDs[i] ); } }
bool CDedicatedServerWorkshopManager::CurrentLevelNeedsUpdate( void ) const { return m_bCurrentLevelNeedsUpdate; }
void CDedicatedServerWorkshopManager::CheckForNewVersion( PublishedFileId_t id ) { bool bUpdateWorkshopCollectionToo = true; if ( m_fTimeLastVersionCheck != 0 && ( Plat_FloatTime() - m_fTimeLastVersionCheck < sv_ugc_manager_max_new_file_check_interval_secs.GetFloat() ) ) { // If we don't have the map then we have to actually go and download it regardless of the timeout
MapFileIdToUgcFileInfo_t::IndexType_t idxFileInfo = m_UGCFileInfos.Find( id ); if ( ( idxFileInfo != m_UGCFileInfos.InvalidIndex() ) && m_UGCFileInfos.Element( idxFileInfo ) ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Skipping new version check for file id %llu, next check in %.2f seconds\n", id, sv_ugc_manager_max_new_file_check_interval_secs.GetFloat() - (Plat_FloatTime() - m_fTimeLastVersionCheck) ); return; }
// We have recently checked the contents of workshop collection,
// this time we are downloading some other map that users want
// to play, so don't check collection
bUpdateWorkshopCollectionToo = false; }
UpdateFile( id );
if ( !bUpdateWorkshopCollectionToo ) return;
// Remember last time we did full update
m_fTimeLastVersionCheck = Plat_FloatTime();
// check if the map collection changed
if ( g_pGameTypes->IsWorkshopMapGroup( gpGlobals->mapGroupName.ToCStr() ) ) { PublishedFileId_t hostedCollectionID = V_atoui64( gpGlobals->mapGroupName.ToCStr() );
// If collection's id is the map's id, then this is a mapgroup of one... no need to check for collection changes.
if ( hostedCollectionID != id ) { if ( !m_CollectionInfoQueries.HasElement( hostedCollectionID ) ) m_CollectionInfoQueries.AddToTail( hostedCollectionID );
m_bHostedCollectionUpdatePending = true; } } }
const char* CDedicatedServerWorkshopManager::GetUGCMapPath( PublishedFileId_t id ) const { int idx = m_mapWorkshopIdsToMapNames.Find( id ); if ( idx != m_mapWorkshopIdsToMapNames.InvalidIndex() ) { return m_mapWorkshopIdsToMapNames[idx]; } else { return NULL; } }
PublishedFileId_t CDedicatedServerWorkshopManager::ParseCollectionInfo( KeyValues * pDetails ) { int collection_detail_result = pDetails->GetInt( "result", 0 ); EResult hResult = (EResult)collection_detail_result; KeyValues *pChildren = pDetails->FindKey( "children" ); PublishedFileId_t ret = 0; if ( hResult == k_EResultOK && pChildren ) { PublishedFileId_t collectionId = pDetails->GetUint64( "publishedfileid", 0 ); if ( sv_debug_ugc_downloads.GetBool() ) { Msg( "Received info for collection id %llu:\n", collectionId ); }
CUtlVector<PublishedFileId_t> vecCollectionIDs; for ( KeyValues *pFile = pChildren->GetFirstSubKey(); pFile != NULL; pFile = pFile->GetNextKey() ) { PublishedFileId_t id = pFile->GetUint64( "publishedfileid" ); vecCollectionIDs.AddToTail( id ); if ( sv_debug_ugc_downloads.GetBool() ) Msg( " file ID: %llu\n", id ); }
if ( ShouldUpdateCollection( collectionId, vecCollectionIDs ) ) { UpdateFiles( vecCollectionIDs ); OnCollectionInfoReceived( collectionId, vecCollectionIDs ); }
ret = collectionId; }
return ret; }
CStreamingUGCDownloader::CStreamingUGCDownloader():m_fileBuffer( 1024*1024, 1024*1024, 0 ) { m_ioAsyncControl = NULL; m_unChunkSize = 0; m_unBytesReceived = 0; m_unFileSizeInBytes = 0; m_pFileInfo = NULL; m_bIsFinished = false; m_bHTTPRequestPending = false; m_flTimeLastMessage = 0.0f; }
void CStreamingUGCDownloader::Cleanup( void ) { if ( m_ioAsyncControl ) { filesystem->AsyncAbort( m_ioAsyncControl ); filesystem->AsyncFinish( m_ioAsyncControl, true ); filesystem->AsyncRelease( m_ioAsyncControl ); m_ioAsyncControl = NULL; } m_fileBuffer.Clear();
if ( filesystem->FileExists( m_szTempFileName ) ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Clearing temp file(%s) for %llu : %s\n", m_szTempFileName, m_pFileInfo->fileId, m_pFileInfo->m_szFileName ); filesystem->RemoveFile( m_szTempFileName ); }
if ( steamgameserverapicontext ) { ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); if ( pHTTP && m_bHTTPRequestPending ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Canceling download for %llu : %s\n", m_pFileInfo->fileId, m_pFileInfo->m_szFileName ); pHTTP->ReleaseHTTPRequest( m_hReq ); } }
m_httpRequestCallback.Cancel(); }
CStreamingUGCDownloader::~CStreamingUGCDownloader() { Cleanup(); }
void CStreamingUGCDownloader::StartFileDownload( const DedicatedServerUGCFileInfo_t *pFileInfo, uint32 unChunkSize ) { m_bIsFinished = false; V_snprintf( m_szTempFileName, ARRAYSIZE( m_szTempFileName ), "%s/%llu/%llu.tmp", g_szWorkshopMapBasePath, pFileInfo->fileId, pFileInfo->fileId );
// Make sure target directory exists
char buf[MAX_PATH]; V_ExtractFilePath( m_szTempFileName, buf, sizeof( buf ) ); g_pFullFileSystem->CreateDirHierarchy( buf, "DEFAULT_WRITE_PATH" );
if ( filesystem->FileExists( m_szTempFileName, "MOD" ) ) { filesystem->RemoveFile(m_szTempFileName, "MOD" ); }
m_pFileInfo = pFileInfo; m_unBytesReceived = 0; m_unFileSizeInBytes = pFileInfo->m_unFileSizeInBytes;
m_unChunkSize = unChunkSize; m_fileBuffer.EnsureCapacity( m_unChunkSize );
// Doing one download at a time-- don't start requesting content until it's this downloader's turn.
// HTTPRequestPartialContent( 0, unChunkSize );
V_strcpy_safe( m_szMapTitle, pFileInfo->m_szTitle );
if ( sv_debug_ugc_downloads.GetBool() ) { Msg( "Starting download for file id %llu:'%s'.\n", pFileInfo->fileId, pFileInfo->m_szTitle ? pFileInfo->m_szTitle : "<no title>" ); }
if ( sv_broadcast_ugc_downloads.GetBool() ) { UTIL_ClientPrintAll( HUD_PRINTTALK, CFmtStr( "Server: Downloading new map '%s', please wait...", pFileInfo->m_szTitle ) ); m_flTimeLastMessage = gpGlobals->curtime; } }
void CStreamingUGCDownloader::HTTPRequestPartialContent( uint32 rangeStart, uint32 rangeEnd ) { Assert( steamgameserverapicontext ); if ( steamgameserverapicontext == NULL ) return;
ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); Assert( pHTTP ); if ( !pHTTP ) return;
m_hReq = pHTTP->CreateHTTPRequest( k_EHTTPMethodGET, m_pFileInfo->m_szUrl ); SteamAPICall_t hCall; CFmtStr byteRange( "bytes=%d-%d", rangeStart, rangeEnd ); pHTTP->SetHTTPRequestHeaderValue( m_hReq, "range", byteRange.Access() ); pHTTP->SendHTTPRequest( m_hReq, &hCall ); m_httpRequestCallback.SetGameserverFlag(); m_httpRequestCallback.Set( hCall, this, &CStreamingUGCDownloader::OnHTTPRequestComplete ); m_bHTTPRequestPending = true;
if ( sv_broadcast_ugc_downloads.GetBool() && gpGlobals->curtime - m_flTimeLastMessage > sv_broadcast_ugc_download_progress_interval.GetFloat() ) { UTIL_ClientPrintAll( HUD_PRINTTALK, CFmtStr( "Server: %.0f%% downloaded for '%s'...", ((float)rangeStart / (float)m_unFileSizeInBytes) * 100.0f, m_szMapTitle ) ); m_flTimeLastMessage = gpGlobals->curtime; } }
void CStreamingUGCDownloader::OnHTTPRequestComplete( HTTPRequestCompleted_t *arg, bool bFailed ) { Assert( steamgameserverapicontext ); if ( steamgameserverapicontext == NULL ) return;
ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); Assert( pHTTP ); if ( !pHTTP ) return;
Assert( arg ); if ( !arg ) return;
if ( arg->m_eStatusCode == k_EHTTPStatusCode206PartialContent ) { uint32 unBodySize; if ( pHTTP->GetHTTPResponseBodySize( arg->m_hRequest, &unBodySize ) ) { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Receiving bytes %u-%u for file %s (%s)\n", m_unBytesReceived, m_unBytesReceived + unBodySize, m_szTempFileName, m_pFileInfo->m_szFileName );
m_unBytesReceived += unBodySize; m_fileBuffer.EnsureCapacity( unBodySize ); m_fileBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, unBodySize ); if ( pHTTP->GetHTTPResponseBodyData( arg->m_hRequest, (uint8*)m_fileBuffer.Base(), unBodySize ) ) { filesystem->AsyncAppend( m_szTempFileName, (void*)m_fileBuffer.Base(), unBodySize, false, &m_ioAsyncControl ); } }
// todo FAIL-- abort
} pHTTP->ReleaseHTTPRequest( arg->m_hRequest ); m_bHTTPRequestPending = false; }
void CStreamingUGCDownloader::Update( void ) { Assert( steamgameserverapicontext ); if ( steamgameserverapicontext == NULL ) return;
ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); Assert( pHTTP ); if ( !pHTTP ) return;
// Free to ask for more content if async write is done, or if we haven't started writing yet.
bool bDoneWriting = m_ioAsyncControl == NULL || filesystem->AsyncStatus( m_ioAsyncControl ) == FSASYNC_OK; if ( bDoneWriting == true && m_bHTTPRequestPending == false ) { if ( m_unBytesReceived < m_unFileSizeInBytes ) { HTTPRequestPartialContent( m_unBytesReceived, MIN( m_unBytesReceived + m_unChunkSize, m_unFileSizeInBytes ) - 1 ); } else { // remove the older file if it exists
//BUG: If we're running this map, the copy will fail. Defer in that case.
if ( filesystem->FileExists( m_pFileInfo->m_szFilePath ) ) { filesystem->RemoveFile( m_pFileInfo->m_szFilePath ); }
// If authors rename the map file, old versions get orphaned in the workshop directory. Nuke any bsp here.
if ( sv_remove_old_ugc_downloads.GetBool() ) { CUtlVector<CUtlString> outList; AddFilesToList( outList, CFmtStr( "%s/%llu/", g_szWorkshopMapBasePath, m_pFileInfo->fileId ).Access(), "MOD", "bsp" ); FOR_EACH_VEC( outList, i ) { filesystem->RemoveFile( outList[i] ); } }
char szFullPathToTempFile[MAX_PATH]; g_pFullFileSystem->RelativePathToFullPath( m_szTempFileName, "MOD", szFullPathToTempFile, sizeof( szFullPathToTempFile ) ); if ( UnzipFile( szFullPathToTempFile ) == false ) { // Not a zip file, just rename it
g_pFullFileSystem->RenameFile( m_szTempFileName, m_pFileInfo->m_szFilePath ); }
//
// Timestamp the file to match workshop updated timestamp
//
UGCUtil_TimestampFile( m_pFileInfo->m_szFilePath, m_pFileInfo->m_unTimeLastUpdated );
m_bIsFinished = true; if ( 1 )//sv_debug_ugc_downloads.GetBool() )
{ Msg( "Download finished for %llu:'%s'. Moving %s to %s.\n", m_pFileInfo->fileId, m_pFileInfo->m_szTitle ? m_pFileInfo->m_szTitle : "<no title>", m_szTempFileName, m_pFileInfo->m_szFilePath ); } } } }
void CWorkshopMapGroupBuilder::OnMapDownloaded( const DedicatedServerUGCFileInfo_t *pInfo ) { MapOnDisk( pInfo->fileId, pInfo->m_szFilePath ); }
CWorkshopMapGroupBuilder::CWorkshopMapGroupBuilder( PublishedFileId_t id, const CUtlVector< PublishedFileId_t >& mapFileIDs ) { m_id = id; m_pendingMapInfos.AddVectorToTail( mapFileIDs ); FOR_EACH_VEC( m_pendingMapInfos, i ) { const char* szPath = DedicatedServerWorkshop().GetUGCMapPath( m_pendingMapInfos[i] ); if ( szPath ) { MapOnDisk( m_pendingMapInfos[i], szPath ); } } }
void CWorkshopMapGroupBuilder::CreateOrUpdateMapGroup( void ) { g_pGameTypes->CreateOrUpdateWorkshopMapGroup( CFmtStr( "%llu", m_id ).Access(), m_Maps ); }
const char* CWorkshopMapGroupBuilder::GetFirstMap( void ) const { return m_Maps.Count() > 0 ? m_Maps.Head() : NULL; }
const char* CWorkshopMapGroupBuilder::GetMapMatchingId( PublishedFileId_t id ) const { FOR_EACH_VEC( m_Maps, i ) { if ( id == GetMapIDFromMapPath( m_Maps[i] ) ) return m_Maps[i]; } return GetFirstMap(); }
void CWorkshopMapGroupBuilder::RemoveRequiredMap( PublishedFileId_t id ) { m_pendingMapInfos.FindAndFastRemove( id ); }
void CWorkshopMapGroupBuilder::MapOnDisk( PublishedFileId_t id, const char* szPath ) { int idx = m_pendingMapInfos.Find( id ); if ( idx != m_pendingMapInfos.InvalidIndex() ) { m_pendingMapInfos.FastRemove( idx );
// Build path to the map file without any extensions and without the 'maps' dir in the path (maps dir is assumed by other systems).
char szMapPath[MAX_PATH]; char szInputPath[MAX_PATH]; V_strcpy_safe( szInputPath, szPath ); V_FixSlashes( szInputPath, '/' ); const char* szMapsPrefix = "maps/"; if ( V_stristr( szInputPath, szMapsPrefix ) == szInputPath ) { V_strcpy_safe( szMapPath, szInputPath + strlen( szMapsPrefix ) ); } V_StripExtension( szMapPath, szMapPath, sizeof( szMapPath ) );
m_Maps.CopyAndAddToTail( szMapPath ); // CUtlStringList auto purges on destruct
} }
CBaseWorkshopHTTPRequest::CBaseWorkshopHTTPRequest( const CUtlVector<PublishedFileId_t> &vecFileIDs ) { m_handle = INVALID_HTTPREQUEST_HANDLE; m_bFinished = false;
m_vecItemsQueried.AddVectorToTail( vecFileIDs ); }
CBaseWorkshopHTTPRequest::~CBaseWorkshopHTTPRequest() { if ( steamgameserverapicontext ) { ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); if ( pHTTP && m_handle != INVALID_HTTPREQUEST_HANDLE ) { pHTTP->ReleaseHTTPRequest( m_handle ); } }
m_httpCallback.Cancel(); }
void CBaseWorkshopHTTPRequest::OnHTTPRequestComplete( HTTPRequestCompleted_t *arg, bool bFailed ) { m_bFinished = true; m_lastHTTPResult = arg->m_eStatusCode;
ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP();
if ( arg->m_bRequestSuccessful == false ) { Warning( "Server UGC Manager: Failed to get file info. Internal IHTTP error or clientside internet connection problem." ); } else if ( arg->m_eStatusCode != k_EHTTPStatusCode200OK || sv_test_steam_connection_failure.GetBool() ) { Warning( "Server UGC Manager: Failed to get file info. HTTP status %d \n", arg->m_eStatusCode ); } else { uint32 unBodySize; if ( !pHTTP->GetHTTPResponseBodySize( arg->m_hRequest, &unBodySize ) ) { Assert( 0 ); Warning( "Server UGC Manager: GetHTTPResponseBodySize failed\n" ); } else { if ( sv_debug_ugc_downloads.GetBool() ) Msg( "Fetched %d bytes via HTTP:\n", unBodySize ); if ( unBodySize > 0 ) { CUtlBuffer resBuffer( 0, unBodySize, 0 ); resBuffer.SetBufferType( true, true ); resBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, unBodySize ); pHTTP->GetHTTPResponseBodyData( arg->m_hRequest, (uint8*)resBuffer.Base(), resBuffer.TellPut() ); KeyValues *pResponseKV = new KeyValues(""); pResponseKV->UsesEscapeSequences( true ); KeyValuesAD autodelete( pResponseKV ); bool bLoadSucessful = pResponseKV->LoadFromBuffer( NULL, resBuffer );
if ( sv_debug_ugc_downloads.GetBool() ) KeyValuesDumpAsDevMsg( pResponseKV, 1 );
if ( !bLoadSucessful ) Msg( "CDedicatedServerWorkshopManager: Failed to load http result to KV buffer\n" );
if ( bLoadSucessful ) { ProcessHTTPResponse( pResponseKV ); } } } }
pHTTP->ReleaseHTTPRequest( arg->m_hRequest ); }
CPublishedFileInfoHTTPRequest::CPublishedFileInfoHTTPRequest( const CUtlVector<PublishedFileId_t>& vecFileIDs ) : CBaseWorkshopHTTPRequest( vecFileIDs ) { }
CPublishedFileInfoHTTPRequest::~CPublishedFileInfoHTTPRequest() { m_vecFileInfos.PurgeAndDeleteElements(); }
HTTPRequestHandle CPublishedFileInfoHTTPRequest::CreateHTTPRequest( const char* szAuthKey ) { if ( steamgameserverapicontext && steamgameserverapicontext->SteamHTTP() ) { CFmtStr strItemCount( "%d", m_vecItemsQueried.Count() ); const char* szUrl = CFmtStr("%s/Service/PublishedFile/GetDetails/v1/", GetApiBaseUrl()).Access(); ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); m_handle = pHTTP->CreateHTTPRequest( k_EHTTPMethodGET, szUrl ); pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, "format", "vdf" ); FOR_EACH_VEC( m_vecItemsQueried, i ) { CFmtStr entry( "publishedfileids[%d]", i ); pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, entry.Access(), CFmtStr("%llu", m_vecItemsQueried[i] ).Access() ); } pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, "key", szAuthKey ); pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, "minimal_details", "1" ); SteamAPICall_t hCall; pHTTP->SendHTTPRequest( m_handle, &hCall); m_httpCallback.SetGameserverFlag(); m_httpCallback.Set( hCall, this, &CBaseWorkshopHTTPRequest::OnHTTPRequestComplete ); } else { m_bFinished = true; } return m_handle; }
void CPublishedFileInfoHTTPRequest::ProcessHTTPResponse( KeyValues *pResponseKV ) { KeyValues *pPublishedFileDetails = pResponseKV->FindKey( "publishedfiledetails", false ); if ( pPublishedFileDetails ) { for ( KeyValues *fileDetails = pPublishedFileDetails->GetFirstSubKey(); fileDetails != NULL; fileDetails = fileDetails->GetNextKey() ) { DedicatedServerUGCFileInfo_t * pNewFileInfo = new DedicatedServerUGCFileInfo_t; pNewFileInfo->BuildFromKV( fileDetails ); m_vecFileInfos.AddToTail( pNewFileInfo ); } } }
CCollectionInfoHTTPRequest::CCollectionInfoHTTPRequest( const CUtlVector<PublishedFileId_t>& vecFileIDs ) : CBaseWorkshopHTTPRequest( vecFileIDs ) { m_pResponseKV = NULL; }
CCollectionInfoHTTPRequest::~CCollectionInfoHTTPRequest() { if ( m_pResponseKV ) { m_pResponseKV->deleteThis(); m_pResponseKV = NULL; } }
HTTPRequestHandle CCollectionInfoHTTPRequest::CreateHTTPRequest( const char* szAuthKey /*= NULL */ ) { if ( steamgameserverapicontext && steamgameserverapicontext->SteamHTTP() ) { CFmtStr strItemCount( "%d", m_vecItemsQueried.Count() ); const char* szUrl = CFmtStr("%s/ISteamRemoteStorage/GetCollectionDetails/v0001/", GetApiBaseUrl()).Access(); ISteamHTTP *pHTTP = steamgameserverapicontext->SteamHTTP(); m_handle = pHTTP->CreateHTTPRequest( k_EHTTPMethodPOST, szUrl ); pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, "format", "vdf" ); pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, "collectioncount", strItemCount.Access() ); FOR_EACH_VEC( m_vecItemsQueried, i ) { CFmtStr entry( "publishedfileids[%d]", i ); pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, entry.Access(), CFmtStr("%llu", m_vecItemsQueried[i] ).Access() ); } pHTTP->SetHTTPRequestGetOrPostParameter( m_handle, "key", szAuthKey ); SteamAPICall_t hCall; pHTTP->SendHTTPRequest( m_handle, &hCall); m_httpCallback.SetGameserverFlag(); m_httpCallback.Set( hCall, this, &CBaseWorkshopHTTPRequest::OnHTTPRequestComplete ); } else { m_bFinished = true; } return m_handle; }
void CCollectionInfoHTTPRequest::ProcessHTTPResponse( KeyValues *pResponseKV ) { KeyValues *pCollectionDetails = pResponseKV->FindKey( "collectiondetails" ); if ( pCollectionDetails ) { Assert( m_pResponseKV == NULL ); if ( m_pResponseKV ) m_pResponseKV->deleteThis();
m_pResponseKV = pCollectionDetails->MakeCopy(); } else { Assert( 0 ); Msg( "CCollectionInfoHTTPRequest: Could not parse response for collection info\n" ); } }
|