|
|
//========= 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
|