//========== 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 &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 & 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& vecFileIDs ); virtual ~CPublishedFileInfoHTTPRequest(); const CUtlVector& GetFileInfoList( void ) const { return m_vecFileInfos; } HTTPRequestHandle CreateHTTPRequest( const char* szAuthKey = NULL ); virtual void ProcessHTTPResponse( KeyValues *pResponseKV ) OVERRIDE; protected: CUtlVector m_vecFileInfos; }; // wrapper for collection info http requests class CCollectionInfoHTTPRequest : public CBaseWorkshopHTTPRequest { public: CCollectionInfoHTTPRequest( const CUtlVector& 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& 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& 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& 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& 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