Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

545 lines
20 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Manager for handling UGC file requests
//
//========================================================================//
#include "cbase.h"
#include "ugc_request_manager.h"
#if !defined(NO_STEAM) && !defined(_PS3)
static uint64 g_TimeStampIncr = 0;
//-----------------------------------------------------------------------------
// LessFunc for UGC operation (priority / timestamp)
//-----------------------------------------------------------------------------
bool UGCOperationsLessFunc( UGCFileRequest_t * const &lhs, UGCFileRequest_t * const &rhs )
{
// If the priorities are equal, then we tie-break on the time they were submitted (rhs wins if another tie occurs)
if ( lhs->GetPriority() == rhs->GetPriority() )
return ( lhs->GetTimestamp() >= rhs->GetTimestamp() );
return ( lhs->GetPriority() < rhs->GetPriority() );
}
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CUGCFileRequestManager::CUGCFileRequestManager( void )
{
m_PendingFileOperations.SetLessFunc( UGCOperationsLessFunc );
m_FileRequests.SetLessFunc( DefLessFunc( UGCHandle_t ) );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CUGCFileRequestManager::DeleteFileRequest( UGCHandle_t handle, bool bRemoveFromDisk /*= false*/ )
{
if ( handle == k_UGCHandleInvalid )
return false;
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( handle );
if ( pRequest == NULL )
return false;
// Clear it from our pending work
for ( int i=0; i < m_PendingFileOperations.Count(); i++ )
{
UGCFileRequest_t *pQueueRequest = m_PendingFileOperations.Element( i );
if ( pQueueRequest && pQueueRequest->fileHandle == handle )
{
m_PendingFileOperations.RemoveAt( i );
break;
}
}
// Remove it from our library
if ( m_FileRequests.Remove( handle ) == false )
return false;
// Clean it off disk as well
if ( bRemoveFromDisk )
{
char szLocalFilename[MAX_PATH];
pRequest->fileRequest.GetFullPath( szLocalFilename, sizeof(szLocalFilename) );
g_pFullFileSystem->RemoveFile( szLocalFilename );
}
delete pRequest;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CUGCFileRequestManager::Update( void )
{
while ( m_PendingFileOperations.Count() )
{
UGCFileRequest_t *pFileRequest = m_PendingFileOperations.ElementAtHead();
Assert( pFileRequest != NULL );
if ( pFileRequest == NULL )
{
// FIXME: Throw a warning
m_PendingFileOperations.RemoveAtHead();
continue;
}
UGCFileRequestStatus_t ugcStatus = pFileRequest->fileRequest.Update();
switch ( ugcStatus )
{
case UGCFILEREQUEST_ERROR:
{
Warning("An error occurred while attempting to download a file from the UGC server!\n");
// Msg("- Deleted: %llu\tPriority:%u\tTimestamp:%u\n", pFileRequest->fileHandle, pFileRequest->unPriority, pFileRequest->unTimestamp );
m_PendingFileOperations.RemoveAtHead();
}
break;
case UGCFILEREQUEST_FINISHED:
{
// If we finished an upload, we need to move the file over into the main library
// FIXME: The library is usually downloaded files ready on disk. Now it means things in the clouds or things on disk...
if ( pFileRequest->nType == UGC_REQUEST_UPLOAD )
{
// If this is invalid, we didn't capture our final file handle properly
Assert( pFileRequest->fileRequest.GetCloudHandle() != k_UGCHandleInvalid );
if ( pFileRequest->fileRequest.GetCloudHandle() != k_UGCHandleInvalid )
{
pFileRequest->fileHandle = pFileRequest->fileRequest.GetCloudHandle();
// Add this into the main list now that it's completed
m_FileRequests.Insert( pFileRequest->fileHandle, pFileRequest );
Log_Msg( LOG_WORKSHOP, "[CUGCRequestManager] Finished uploading %llu\n", pFileRequest->fileHandle );
}
}
else
{
Log_Msg( LOG_WORKSHOP, "[CUGCRequestManager] Finished downloading %llu\n", pFileRequest->fileHandle );
IGameEvent *pEvent = gameeventmanager->CreateEvent( "ugc_file_download_finished" );
if ( pEvent )
{
pEvent->SetUint64( "hcontent", pFileRequest->GetFileHandle() );
gameeventmanager->FireEventClientSide( pEvent );
}
}
// We're done, continue on!
// Msg("- Deleted: %llu\tPriority:%u\tTimestamp:%u\n", pFileRequest->fileHandle, pFileRequest->unPriority, pFileRequest->unTimestamp );
m_PendingFileOperations.RemoveAtHead();
}
break;
case UGCFILEREQUEST_READY:
{
if ( pFileRequest->nType == UGC_REQUEST_DOWNLOAD )
{
// Pass along target directory and filename unless they're not set
const char *lpszTargetDirectory = ( pFileRequest->szTargetDirectory[0] != '\0' ) ? pFileRequest->szTargetDirectory : NULL;
const char *lpszTargetFilename = ( pFileRequest->szTargetFilename[0] != '\0' ) ? pFileRequest->szTargetFilename : NULL;
// We're ready to download, so start us off
UGCFileRequestStatus_t status = pFileRequest->fileRequest.StartDownload( pFileRequest->fileHandle, lpszTargetDirectory, lpszTargetFilename, pFileRequest->unLastUpdateTime, pFileRequest->bForceUpdate );
if ( status == UGCFILEREQUEST_FINISHED )
{
// We're already done (file was on disk)
// FIXME: Roll this into the function call above!
// Msg("- Deleted: %llu\tPriority:%u\tTimestamp:%u\n", pFileRequest->fileHandle, pFileRequest->unPriority, pFileRequest->unTimestamp );
m_PendingFileOperations.RemoveAtHead();
}
if ( status == UGCFILEREQUEST_DOWNLOADING )
{
IGameEvent *pEvent = gameeventmanager->CreateEvent( "ugc_file_download_start" );
if ( pEvent )
{
pEvent->SetUint64( "hcontent", pFileRequest->GetFileHandle() );
pEvent->SetUint64( "published_file_id", pFileRequest->GetPublishedFileID() );
gameeventmanager->FireEventClientSide( pEvent );
}
}
Log_Msg( LOG_WORKSHOP, "[CUGCRequestManager] Beginning download of %llu\n", pFileRequest->fileHandle );
return;
}
else if ( pFileRequest->nType == UGC_REQUEST_UPLOAD )
{
const char *lpszTargetDirectory = ( pFileRequest->szTargetDirectory[0] != '\0' ) ? pFileRequest->szTargetDirectory : NULL;
const char *lpszTargetFilename = ( pFileRequest->szTargetFilename[0] != '\0' ) ? pFileRequest->szTargetFilename : NULL;
char szFullPath[MAX_PATH];
V_SafeComposeFilename( lpszTargetDirectory, lpszTargetFilename, szFullPath, ARRAYSIZE(szFullPath) );
// FIXME: Bleh, this makes all kinds of contracts we don't like!
CUtlBuffer buffer;
// FIXME: Swap for an async read!
if ( !g_pFullFileSystem->ReadFile( pFileRequest->szSourceFilename, "GAME", buffer ) )
{
// We failed to read this off the disk
buffer.Purge();
pFileRequest->fileRequest.ThrowError( "Unable to read file: %s\n", pFileRequest->szSourceFilename );
return;
}
// We're ready to download, so start us off
UGCFileRequestStatus_t status = pFileRequest->fileRequest.StartUpload( buffer, szFullPath );
if ( status == UGCFILEREQUEST_ERROR )
{
// FIXME: Now what?
// m_PendingFileOperations.RemoveAtHead();
Assert( 0 );
}
Log_Msg( LOG_WORKSHOP, "[CUGCRequestManager] Beginning upload of %s\n", szFullPath );
// Done with the memory
buffer.Purge();
return;
}
}
break;
default:
// Working, continue to wait...
return;
break;
}
// The request is complete, continue to the next!
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CUGCFileRequestManager::CreateFileDownloadRequest( UGCHandle_t unFileHandle, PublishedFileId_t fileID, const char *lpszTargetDirectory, const char *lpszTargetFilename, uint32 unPriority, uint32 unLastUpdateTime /*=0*/, bool bForceUpdate /*= false*/ )
{
// Must pass in a valid handle if we're downloading
if ( unFileHandle == k_UGCHandleInvalid )
return false;
// Make sure we don't already have a request by this handle
if ( FileRequestExists( unFileHandle ) )
return true;
UGCFileRequest_t *pRequest = new UGCFileRequest_t;
pRequest->nType = UGC_REQUEST_DOWNLOAD;
pRequest->fileHandle = unFileHandle;
pRequest->publishedFileID = fileID;
if ( lpszTargetDirectory != NULL )
{
V_strncpy( pRequest->szTargetDirectory, lpszTargetDirectory, ARRAYSIZE(pRequest->szTargetDirectory) );
V_FixSlashes( pRequest->szTargetDirectory );
}
if ( lpszTargetFilename != NULL )
{
V_strncpy( pRequest->szTargetFilename, lpszTargetFilename, ARRAYSIZE(pRequest->szTargetFilename) );
}
pRequest->unLastUpdateTime = unLastUpdateTime;
pRequest->bForceUpdate = bForceUpdate;
pRequest->unTimestamp = g_TimeStampIncr++; // FIXME: This is to get around some timestamping, in essence larger numbers = newer additions
pRequest->unPriority = unPriority;
// This insert will sort the request into the list properly
m_PendingFileOperations.Insert( pRequest );
// For debugging insertion into priority queue
Log_Msg( LOG_WORKSHOP, "[CUGCFileRequestManager] Inserted Download: %llu\tPriority:%u\tTimestamp:%u\n", pRequest->fileHandle, pRequest->unPriority, pRequest->unTimestamp );
Debug_LogPendingOperations();
// Keep this in our records now
m_FileRequests.Insert( unFileHandle, pRequest );
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CUGCFileRequestManager::CreateFileUploadRequest( const char *lpszSourceFilename, const char *lpszTargetDirectory, const char *lpszTargetFilename, uint32 unPriority )
{
if ( lpszSourceFilename == NULL )
return false;
// Make sure we don't already have a request for this
char szFullPath[MAX_PATH];
V_SafeComposeFilename( lpszTargetDirectory, lpszTargetFilename, szFullPath, ARRAYSIZE(szFullPath) );
// Make sure we don't have another upload by the same filename in progress
const UGCFileRequest_t *pDuplicateRequest = GetFileRequestByFilename( szFullPath );
if ( pDuplicateRequest != NULL && pDuplicateRequest->nType == UGC_REQUEST_UPLOAD )
return true;
UGCFileRequest_t *pRequest = new UGCFileRequest_t;
pRequest->nType = UGC_REQUEST_UPLOAD;
pRequest->fileHandle = k_UGCHandleInvalid;
if ( lpszTargetDirectory != NULL )
{
V_strncpy( pRequest->szTargetDirectory, lpszTargetDirectory, ARRAYSIZE(pRequest->szTargetDirectory) );
V_FixSlashes( pRequest->szTargetDirectory );
}
if ( lpszTargetFilename != NULL )
{
V_strncpy( pRequest->szTargetFilename, lpszTargetFilename, ARRAYSIZE(pRequest->szTargetFilename) );
}
// Save where we're going to read from
V_strncpy( pRequest->szSourceFilename, lpszSourceFilename, ARRAYSIZE(pRequest->szSourceFilename) );
V_FixSlashes( pRequest->szSourceFilename );
pRequest->unLastUpdateTime = 0;
pRequest->bForceUpdate = false;
pRequest->unTimestamp = g_TimeStampIncr++; // FIXME: This is to get around some timestamping, in essence larger numbers = newer additions
pRequest->unPriority = unPriority;
// This insert will sort the request into the list properly
m_PendingFileOperations.Insert( pRequest );
// For debugging insertion into priority queue
Log_Msg( LOG_WORKSHOP, "[CUGCFileRequestManager] Inserted Upload: %llu\tPriority:%u\tTimestamp:%u\n", pRequest->fileHandle, pRequest->unPriority, pRequest->unTimestamp );
Debug_LogPendingOperations();
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const UGCFileRequest_t *CUGCFileRequestManager::GetFileRequestByHandle( UGCHandle_t unFileHandle ) const
{
int nIndex = m_FileRequests.Find( unFileHandle );
if ( nIndex == m_FileRequests.InvalidIndex() )
return NULL;
return m_FileRequests[nIndex];
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CUGCFileRequestManager::FileRequestExists( UGCHandle_t handle ) const
{
int nIndex = m_FileRequests.Find( handle );
return ( nIndex != m_FileRequests.InvalidIndex() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CUGCFileRequestManager::GetFullPath( UGCHandle_t unFileHandle, char *pDest, size_t nSize ) const
{
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( unFileHandle );
if ( pRequest == NULL )
{
// Clear the return so it's obvious it failed
V_memset( pDest, 0, nSize );
return;
}
pRequest->fileRequest.GetFullPath( pDest, nSize );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CUGCFileRequestManager::GetDirectory( UGCHandle_t unFileHandle ) const
{
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( unFileHandle );
if ( pRequest == NULL )
return NULL;
return pRequest->fileRequest.GetDirectory();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CUGCFileRequestManager::GetFilename( UGCHandle_t unFileHandle ) const
{
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( unFileHandle );
if ( pRequest == NULL )
return NULL;
return pRequest->fileRequest.GetFilename();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
UGCFileRequestStatus_t CUGCFileRequestManager::GetStatus( UGCHandle_t unFileHandle ) const
{
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( unFileHandle );
if ( pRequest == NULL )
return UGCFILEREQUEST_INVALID;
return pRequest->fileRequest.GetStatus();
}
//-----------------------------------------------------------------------------
// Purpose: Get the file handle for a request by its target filename
//-----------------------------------------------------------------------------
UGCHandle_t CUGCFileRequestManager::GetFileRequestHandleByFilename( const char *lpszFilename ) const
{
// Get the request by name
const UGCFileRequest_t *pRequest = GetFileRequestByFilename( lpszFilename );
if ( pRequest != NULL )
{
return pRequest->fileHandle;
}
return k_UGCHandleInvalid;
}
//-----------------------------------------------------------------------------
// Purpose: Get the file handle for a request by its target filename
//-----------------------------------------------------------------------------
const UGCFileRequest_t *CUGCFileRequestManager::GetFileRequestByFilename( const char *lpszFilename ) const
{
// FIXME: This is a slow crawl through a list doing stricmps :(
char szFullPath[MAX_PATH];
for ( unsigned int i=0; i < m_FileRequests.Count(); i++ )
{
const UGCFileRequest_t *pRequest = m_FileRequests[i];
pRequest->fileRequest.GetFullPath( szFullPath, ARRAYSIZE(szFullPath) );
if ( !V_stricmp( szFullPath, lpszFilename ) )
return pRequest;
}
// Now move through all the pending operations to see if it's living in there
// We need to do this because uploads don't live in the normal system until they're done uploading
// FIXME: This is going to be doing duplicate work since items can straddle both the known requests and the pending ones
for ( int i=0; i < m_PendingFileOperations.Count(); i++ )
{
const UGCFileRequest_t *pRequest = m_PendingFileOperations.Element(i);
pRequest->fileRequest.GetFullPath( szFullPath, ARRAYSIZE(szFullPath) );
if ( !V_stricmp( szFullPath, lpszFilename ) )
return pRequest;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
UGCFileRequestStatus_t CUGCFileRequestManager::GetStatus( const char *lpszFilename ) const
{
const UGCFileRequest_t *pRequest = GetFileRequestByFilename( lpszFilename );
if ( pRequest == NULL )
return UGCFILEREQUEST_INVALID;
return pRequest->fileRequest.GetStatus();
}
//
//-----------------------------------------------------------------------------
// Purpose: Returns the progress of a file being downloaded from the Steam cloud.
// Return: Always 0 if nothing has begun, or 1 if past the point of downloading, otherwise, the percentage downloaded
//-----------------------------------------------------------------------------
float CUGCFileRequestManager::GetDownloadProgress( UGCHandle_t handle ) const
{
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( handle );
if ( pRequest == NULL )
return 0.0f;
return pRequest->GetProgress();
}
//-----------------------------------------------------------------------------
// Purpose: Promote the specified handle to the top of the priority list
//-----------------------------------------------------------------------------
bool CUGCFileRequestManager::PromoteRequestToTop( UGCHandle_t handle )
{
// The request must be in the system
const UGCFileRequest_t *pRequest = GetFileRequestByHandle( handle );
if ( pRequest == NULL )
return false;
// There must be pending operations to bother continuing
if ( m_PendingFileOperations.Count() == 0 )
return false;
// If we're already at the top, don't bother
const UGCFileRequest_t *pTopRequest = m_PendingFileOperations.ElementAtHead();
if ( pTopRequest == pRequest )
return true;
// This is the top priority currently
uint32 unTopPriority = pTopRequest->unPriority;
// Now we need to find this request in the pending operations
for ( int i = 0; i < m_PendingFileOperations.Count(); i++ )
{
const UGCFileRequest_t *pFoundRequest = m_PendingFileOperations.Element(i);
if ( pRequest == pFoundRequest )
{
// Bump our priority up
// We cast away the const reference because we're the controlling class for this type
((UGCFileRequest_t *)pRequest)->unPriority = unTopPriority+1;
m_PendingFileOperations.RemoveAt(i);
m_PendingFileOperations.Insert( ((UGCFileRequest_t *)pRequest) );
Log_Msg( LOG_WORKSHOP, "[CUGCFileRequestManager] Promoted %llu to top of queue\n", pRequest->fileHandle );
Debug_LogPendingOperations();
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Dump our priority queue so we can debug it
//-----------------------------------------------------------------------------
void CUGCFileRequestManager::Debug_LogPendingOperations( void )
{
#if 0
// Must have something to operate on
if ( m_PendingFileOperations.Count() == 0 )
return;
// For debugging insertion into priority queue
Log_Msg( LOG_WORKSHOP, "\n==[Pending UGC Operations]==\n");
// The queue cannot be walked through trivially, so we need to actually pop each member off the top, the reinsert at the end
CUtlVector< UGCFileRequest_t * > vecOverflow;
while ( m_PendingFileOperations.Count() )
{
UGCFileRequest_t *pQueuedRequest = m_PendingFileOperations.ElementAtHead();
Log_Msg( LOG_WORKSHOP, "o File: %llu\tPriority:%u\tTimestamp:%u\n", pQueuedRequest->fileHandle, pQueuedRequest->unPriority, pQueuedRequest->unTimestamp );
vecOverflow.AddToTail( pQueuedRequest );
m_PendingFileOperations.RemoveAtHead();
}
// Put them all back
for ( int i=0; i < vecOverflow.Count(); i++ )
{
m_PendingFileOperations.Insert( vecOverflow[i] );
}
Log_Msg( LOG_WORKSHOP, "==============================\n\n");
#endif //
}
bool CUGCFileRequestManager::HasPendingDownloads( void ) const
{
return m_PendingFileOperations.Count() > 0;
}
#endif // ! NO_STEAM