csgo-2018-source/game/server/dedicated_server_ugc_manager.h
2021-07-24 21:11:47 -07:00

281 lines
12 KiB
C++

//========== Copyright © Valve Corporation, All rights reserved. ========
//
// Dedicated server's object for managing UGC file subscriptions.
//
//=============================================================================
#ifndef DEDICATED_SERVER_UGC_MANAGER
#define DEDICATED_SERVER_UGC_MANAGER
#if defined( COMPILER_MSVC )
#pragma once
#endif
#define AUTH_KEY_MAX_LEN 64
#include "igamesystem.h" // autogamesystem base class
#include "steam/isteamremotestorage.h" // ugc types/constants
// Class for holding info about a UGC file the server is 'subscribed' to.
struct DedicatedServerUGCFileInfo_t
{
// Pass in a 'publishfiledetails' kv blob returned by the 'GetPublishedFileDetails' web api.
bool BuildFromKV( KeyValues *pPublishedFileDetails );
char m_szFileName[k_cchFilenameMax]; // name_on_disk.extention
uint32 m_unFileSizeInBytes; // size in bytes
char m_szTitle[k_cchPublishedDocumentTitleMax]; // Display name
char m_szUrl[k_cchPublishedFileURLMax]; // File url
uint32 m_unTimeLastUpdated; // Last update to this content
PublishedFileId_t fileId; // UGC id for this content package (file + preview + metadata). Always the same after updates.
UGCHandle_t contentHandle; // UGC content handle to the file we're downloading. This can change if the author updates the file
char m_szFilePath[MAX_PATH]; // Path to store UGC file relative to game directory
bool m_bIsValid; // Did we get all the fields we need from the publish file details KV?
double m_dblPlatFloatTimeReceived; // Plat_FloatTime of when we received this information
EResult m_result; // Result for this entry we got back from steam (is 0 if no problems)
};
// Requests a file from steam over http one unChunkSize at a time, async writes to disk.
class CStreamingUGCDownloader
{
public:
virtual ~CStreamingUGCDownloader();
CStreamingUGCDownloader();
void StartFileDownload( const DedicatedServerUGCFileInfo_t *pFileInfo, uint32 unChunkSize );
bool IsFinished( void ) { return m_bIsFinished; }
void Update ( void );
PublishedFileId_t GetPublishedFileId( void ) { return m_pFileInfo ? m_pFileInfo->fileId : 0; }
const DedicatedServerUGCFileInfo_t* GetFileInfo( void ) { return m_pFileInfo; }
private:
CCallResult< CStreamingUGCDownloader, HTTPRequestCompleted_t > m_httpRequestCallback;
void OnHTTPRequestComplete( HTTPRequestCompleted_t *arg, bool bFailed );
void HTTPRequestPartialContent( uint32 rangeStart, uint32 rangeEnd );
void Cleanup( void );
uint32 m_unChunkSize;
uint32 m_unBytesReceived;
uint32 m_unFileSizeInBytes;
char m_szTempFileName[k_cchFilenameMax]; // stream to a temp file as we download, copy to final dest once we get it all
CUtlBuffer m_fileBuffer;
FSAsyncControl_t m_ioAsyncControl;
const DedicatedServerUGCFileInfo_t *m_pFileInfo;
bool m_bIsFinished;
bool m_bHTTPRequestPending;
HTTPRequestHandle m_hReq;
float m_flTimeLastMessage;
char m_szMapTitle[k_cchPublishedDocumentTitleMax];
// no copy, no assign.
CStreamingUGCDownloader( const CStreamingUGCDownloader& src );
CStreamingUGCDownloader& operator=( const CStreamingUGCDownloader& src );
};
// Wrapper for steam api file info requests.
class CBaseWorkshopHTTPRequest
{
public:
CBaseWorkshopHTTPRequest( const CUtlVector<PublishedFileId_t> &vecFileIDs );
virtual ~CBaseWorkshopHTTPRequest();
void OnHTTPRequestComplete( HTTPRequestCompleted_t *arg, bool bFailed );
bool IsFinished( void ) const { return m_bFinished; }
EHTTPStatusCode GetLastHTTPResult( void ) const { return m_lastHTTPResult; }
const CUtlVector<PublishedFileId_t> & GetItemsQueried( void ) const { return m_vecItemsQueried; }
protected:
virtual void ProcessHTTPResponse( KeyValues *pResponseKV ) { Assert( 0 ); };
CUtlVector< PublishedFileId_t > m_vecItemsQueried;
HTTPRequestHandle m_handle;
EHTTPStatusCode m_lastHTTPResult;
bool m_bFinished;
CCallResult< CBaseWorkshopHTTPRequest, HTTPRequestCompleted_t > m_httpCallback;
CBaseWorkshopHTTPRequest( const CBaseWorkshopHTTPRequest& src );
CBaseWorkshopHTTPRequest& operator=( const CBaseWorkshopHTTPRequest& src );
};
// Turns a list of file ids into filled out info structs
class CPublishedFileInfoHTTPRequest : public CBaseWorkshopHTTPRequest
{
public:
CPublishedFileInfoHTTPRequest( const CUtlVector<PublishedFileId_t>& vecFileIDs );
virtual ~CPublishedFileInfoHTTPRequest();
const CUtlVector<DedicatedServerUGCFileInfo_t*>& GetFileInfoList( void ) const { return m_vecFileInfos; }
HTTPRequestHandle CreateHTTPRequest( const char* szAuthKey = NULL );
virtual void ProcessHTTPResponse( KeyValues *pResponseKV ) OVERRIDE;
protected:
CUtlVector<DedicatedServerUGCFileInfo_t*> m_vecFileInfos;
};
// wrapper for collection info http requests
class CCollectionInfoHTTPRequest : public CBaseWorkshopHTTPRequest
{
public:
CCollectionInfoHTTPRequest( const CUtlVector<PublishedFileId_t>& vecFileIDs );
virtual ~CCollectionInfoHTTPRequest();
HTTPRequestHandle CreateHTTPRequest( const char* szAuthKey = NULL );
virtual void ProcessHTTPResponse( KeyValues *pResponseKV ) OVERRIDE;
KeyValues* GetResponseKV( void ) { return m_pResponseKV; }
protected:
KeyValues* m_pResponseKV;
};
// Keeps track of a map or collection of maps to turn into a map group in the GameTypes system once they've all been updated.
class CWorkshopMapGroupBuilder
{
public:
CWorkshopMapGroupBuilder( PublishedFileId_t id, const CUtlVector< PublishedFileId_t >& m_mapFileIDs );
void MapOnDisk( PublishedFileId_t id, const char* szPath );
void OnMapDownloaded( const DedicatedServerUGCFileInfo_t* pInfo );
bool IsFinished ( void ) const { return m_pendingMapInfos.Count() == 0; }
PublishedFileId_t GetId() const { return m_id; }
const char* GetFirstMap( void ) const;
const char* GetMapMatchingId( PublishedFileId_t id ) const;
void CreateOrUpdateMapGroup( void );
void RemoveRequiredMap( PublishedFileId_t id );
private:
CUtlVector< PublishedFileId_t > m_pendingMapInfos; // Maps we still need the latest version of. Removes entries when map is at latest version on disk.
CUtlStringList m_Maps;
PublishedFileId_t m_id; // collection id, or map id if a single map.
CWorkshopMapGroupBuilder( const CWorkshopMapGroupBuilder& src );
CWorkshopMapGroupBuilder& operator=( const CWorkshopMapGroupBuilder& src );
};
class CDedicatedServerWorkshopManager : public CAutoGameSystem
{
public:
// Autogamesystem overrides.
virtual bool Init( void ) OVERRIDE;
virtual void LevelInitPreEntity( void ) OVERRIDE;
virtual const char* Name( void ) OVERRIDE { return "CDedicatedServerMapWorkshop"; }
virtual void Shutdown( void ) OVERRIDE;
// Gathers all the file IDs this server needs to stay up to date with and sends file info queries to steam.
// EXPENSIVE: does file io.
void GetNewestSubscribedFiles( void );
// To support code addressing maps by mapname and assuming the /maps/ directory,
// this method will return a list of DedicatedServerUGCFileInfos whose mapname matches the given string.
// returns true if any maps are filled out. Only returns map infos whose map is already on disk (no pending downloads).
bool GetMapsMatchingName( const char* szMapName, CUtlVector<const DedicatedServerUGCFileInfo_t*>& outVec ) const;
// Get the file ID of a UGC map. Returns 0 if non-ugc map or if not in subscription list.
// Parameter is path to the map relative to the game directory.
PublishedFileId_t GetUGCMapPublishedFileID( const char* szPathToUGCMap ) const;
// Returns the path to the map file for a given file id, or NULL if map is not on disk.
const char* GetUGCMapPath( PublishedFileId_t id ) const;
// This ticks from ServerGameDll::Think... ideally this would be an event
// driven system but we'd need callbacks from the async append for that to work.
void Update( void );
// List of file IDs for subscribed maps on disk.
const CUtlVector< PublishedFileId_t >& GetWorkshopMapList( void ) const;
// Get the latest version of a map or collection and switch to it once the latest version is downloaded.
void HostWorkshopMap( PublishedFileId_t id );
void HostWorkshopMapCollection( PublishedFileId_t id );
bool HasPendingMapDownloads( void ) const;
void CheckForNewVersion( PublishedFileId_t id );
void CheckIfCurrentLevelNeedsUpdate( void );
bool CurrentLevelNeedsUpdate( void ) const;
void SetTargetStartMap( PublishedFileId_t id ) { m_unTargetStartMap = id; }
// Get the maps for which we downloaded UGC information successfully
void GetWorkshopMasWithValidUgcInformation( CUtlVector<const DedicatedServerUGCFileInfo_t *>& outVec ) const;
protected:
void UpdatePublishedFileInfoRequests( void ); // Empties pending url list, calls webapi requesting info for them
// Returns the collection id if sucessful, 0 otherwise.
PublishedFileId_t ParseCollectionInfo( KeyValues * pDetails );
void UpdateUGCDownloadRequests( void ); // http get for the content's URL, stream to disk
bool IsFileLatestVersion( const DedicatedServerUGCFileInfo_t* ugcInfo ); // Test file timestamp vs last update time.
void UpdateFiles( const CUtlVector<PublishedFileId_t>& vecFileIDs );
void UpdateFile( PublishedFileId_t id );
void OnFileInfoReceived( const DedicatedServerUGCFileInfo_t *pInfo );
void OnFileInfoRequestFailed( PublishedFileId_t id );
void OnCollectionInfoReceived( PublishedFileId_t collectionId, const CUtlVector< PublishedFileId_t > & vecCollectionItems );
void OnCollectionInfoRequestFailed( PublishedFileId_t id );
void OnFileDownloaded( const DedicatedServerUGCFileInfo_t *pInfo );
// Record we have this bsp on disk (may not be up to date)
void NoteWorkshopMapOnDisk( PublishedFileId_t id, const char* szPath );
bool ShouldUpdateCollection( PublishedFileId_t id, const CUtlVector<PublishedFileId_t>& vecMaps );
void RemoveFileInfo ( PublishedFileId_t id );
void Cleanup( void );
void QueueDownloadFile( const DedicatedServerUGCFileInfo_t *pFileInfo );
// List of downloads in progress
CUtlVector< CStreamingUGCDownloader* > m_PendingFileDownloads;
CUtlVector< PublishedFileId_t > m_FileInfoQueries; // info requests yet to be sent
CUtlVector< CPublishedFileInfoHTTPRequest* > m_PendingFileInfoRequests; // info requests in flight
CUtlVector< PublishedFileId_t > m_CollectionInfoQueries; // both use file ids, but if it's an id for a collection we need to call a different webapi
CUtlVector< CCollectionInfoHTTPRequest* > m_PendingCollectionInfoRequests; // collection info requests in flight
CUtlVector< PublishedFileId_t > m_vecMapsBeingUpdated; // IDs of maps either waiting for newest file info or in the process of downloading
CUtlVector< PublishedFileId_t > m_vecWorkshopMapList; // Maps we have on disk
CUtlMap< PublishedFileId_t, CUtlString > m_mapWorkshopIdsToMapNames;
CUtlVector< PublishedFileId_t > m_vecFileQueryRetries;
CUtlVector< PublishedFileId_t > m_vecCollectionQueryRetries;
// Map of files we have queried info for.
typedef CUtlMap< PublishedFileId_t, DedicatedServerUGCFileInfo_t* > MapFileIdToUgcFileInfo_t;
MapFileIdToUgcFileInfo_t m_UGCFileInfos;
// Helper class to keep track of our updating/downloading of a set of maps, then create a map group when we have them all.
CWorkshopMapGroupBuilder* m_pMapGroupBuilder;
PublishedFileId_t m_desiredHostCollection;
PublishedFileId_t m_unTargetStartMap; // supports server ops specifying which map in a collection they wish to start on.
char m_szWebAPIAuthKey[AUTH_KEY_MAX_LEN];
bool m_bFoundAuthKey;
bool m_bCurrentLevelNeedsUpdate; // Only gets updated if external caller requests it... only needed for a special case in cs_gamerules's restarting logic.
PublishedFileId_t m_hackCurrentMapInfoCheck; // HACK: Will skip the next download for a file matching this publish ID, then zero this member.
bool m_bHostedCollectionUpdatePending;
double m_fTimeLastVersionCheck;
CUtlMap< PublishedFileId_t, KeyValues* > m_mapPreviousCollectionQueryCache; // Old collection infos for use when we can't talk to steam.
};
CDedicatedServerWorkshopManager& DedicatedServerWorkshop( void );
#endif // DEDICATED_SERVER_UGC_MANAGER