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.
985 lines
31 KiB
985 lines
31 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Dialog for selecting game configurations |
|
// |
|
//=====================================================================================// |
|
#include "cbase.h" |
|
|
|
#include <vgui/IVGui.h> |
|
#include <vgui/IInput.h> |
|
#include <vgui/ISystem.h> |
|
#include <vgui/ISurface.h> |
|
#include <vgui_controls/Button.h> |
|
#include <vgui_controls/TextEntry.h> |
|
#include <vgui_controls/MessageBox.h> |
|
#include <vgui_controls/ImagePanel.h> |
|
#include <vgui_controls/FileOpenDialog.h> |
|
#include "vgui_bitmappanel.h" |
|
#include <KeyValues.h> |
|
#include "imageutils.h" |
|
#include "bsp_utils.h" |
|
|
|
#include "icommandline.h" |
|
#include "publish_file_dialog.h" |
|
#include "workshop/ugc_utils.h" |
|
|
|
#ifdef TF_CLIENT_DLL |
|
#include "../server/tf/workshop/maps_workshop.h" |
|
#endif // TF_CLIENT_DLL |
|
|
|
#include "steam/steam_api.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
static CUtlString g_MapFilename; |
|
static CUtlString g_PreviewFilename; |
|
|
|
ConVar publish_file_last_dir( "publish_file_last_dir", "", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD ); |
|
|
|
CFilePublishDialog *g_pSteamFilePublishDialog = NULL; |
|
|
|
#define WORKSHOP_TEMP_UPLOAD_DIR "workshop/upload" |
|
|
|
class CPrepareFileThread : public CThread |
|
{ |
|
public: |
|
CPrepareFileThread( const char *pszInputFile, const char *pszOutputFile ) |
|
: m_strInput( pszInputFile ) |
|
, m_strOutput( pszOutputFile ) |
|
{} |
|
|
|
// Return 0 for success |
|
virtual int Run() |
|
{ |
|
if ( V_strcasecmp( V_GetFileExtension( m_strInput.Get() ), "bsp" ) == 0 ) |
|
{ |
|
return BSP_SyncRepack( m_strInput.Get(), m_strOutput.Get() ) ? 0 : 1; |
|
} |
|
else |
|
{ |
|
// Just copy file to prepared location |
|
return engine->CopyLocalFile( m_strInput.Get(), m_strOutput.Get() ) ? 0 : 1; |
|
} |
|
} |
|
|
|
private: |
|
CUtlString m_strInput; |
|
CUtlString m_strOutput; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CFilePublishDialog::CFilePublishDialog( Panel *parent, const char *name, PublishedFileDetails_t *pDetails ) : BaseClass( parent, name ) |
|
{ |
|
m_pPrepareFileThread = NULL; |
|
|
|
g_pSteamFilePublishDialog = this; |
|
|
|
// These must be supplied |
|
m_bValidFile = false; |
|
m_bValidJpeg = false; |
|
|
|
vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); |
|
|
|
// Save this for later |
|
if ( pDetails != NULL ) |
|
{ |
|
m_FileDetails = *pDetails; |
|
m_bAddingNewFile = false; |
|
g_MapFilename = m_FileDetails.lpszFilename; |
|
m_nFileID = m_FileDetails.publishedFileDetails.m_nPublishedFileId; |
|
} |
|
else |
|
{ |
|
// Clear it out |
|
m_FileDetails.lpszFilename = NULL; |
|
m_FileDetails.publishedFileDetails.m_eVisibility = k_ERemoteStoragePublishedFileVisibilityPublic; |
|
m_FileDetails.publishedFileDetails.m_hFile = k_UGCHandleInvalid; |
|
m_FileDetails.publishedFileDetails.m_hPreviewFile = k_UGCHandleInvalid; |
|
m_FileDetails.publishedFileDetails.m_nConsumerAppID = k_uAppIdInvalid; |
|
m_FileDetails.publishedFileDetails.m_nCreatorAppID = k_uAppIdInvalid; |
|
m_FileDetails.publishedFileDetails.m_nPublishedFileId = 0; // FIXME: Need a real "invalid" value |
|
m_FileDetails.publishedFileDetails.m_rtimeCreated = 0; |
|
m_FileDetails.publishedFileDetails.m_rtimeUpdated = 0; |
|
m_FileDetails.publishedFileDetails.m_ulSteamIDOwner = 0; // FIXME: Need a real "invalid" value |
|
memset( m_FileDetails.publishedFileDetails.m_rgchDescription, 0, k_cchPublishedDocumentDescriptionMax ); |
|
memset( m_FileDetails.publishedFileDetails.m_rgchTitle, 0, k_cchPublishedDocumentTitleMax ); |
|
|
|
m_bAddingNewFile = true; |
|
g_MapFilename = ""; |
|
m_nFileID = k_PublishedFileIdInvalid; |
|
} |
|
|
|
m_nFileDetailsChanges = 0; |
|
|
|
m_fileOpenMode = FILEOPEN_NONE; |
|
|
|
// Setup our image panel |
|
m_pCroppedTextureImagePanel = new CBitmapPanel( this, "PreviewImage" ); |
|
m_pCroppedTextureImagePanel->SetSize( DesiredPreviewWidth(), DesiredPreviewHeight() ); |
|
m_pCroppedTextureImagePanel->SetVisible( true ); |
|
|
|
m_pStatusBox = NULL; |
|
|
|
// Start downloading our preview image |
|
m_bPreviewDownloadPending = false; |
|
DownloadPreviewImage(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Destructor |
|
//----------------------------------------------------------------------------- |
|
CFilePublishDialog::~CFilePublishDialog() |
|
{ |
|
//delete m_pConfigCombo; |
|
g_pSteamFilePublishDialog = NULL; |
|
|
|
// We should be in a modal dialog when this is running, not closable |
|
Assert( !m_pPrepareFileThread ); |
|
if ( m_pPrepareFileThread ) |
|
{ |
|
m_pPrepareFileThread->Stop(); |
|
delete m_pPrepareFileThread; |
|
m_pPrepareFileThread = NULL; |
|
} |
|
} |
|
|
|
void CFilePublishDialog::ErrorMessage( ErrorCode_t errorCode, KeyValues *pkvTokens ) |
|
{ |
|
switch ( errorCode ) |
|
{ |
|
case kFailedToPublishFile: |
|
ErrorMessage( "Failed to publish file!" ); |
|
break; |
|
case kFailedToUpdateFile: |
|
ErrorMessage( "Failed to update file!" ); |
|
break; |
|
case kFailedToPrepareFile: |
|
ErrorMessage( "Failed to prepare file!" ); |
|
break; |
|
case kSteamCloudNotAvailable: |
|
ErrorMessage( "Steam Cloud is not available." ); |
|
break; |
|
case kSteamExceededCloudQuota: |
|
ErrorMessage( "Exceed Steam Cloud quota." ); |
|
break; |
|
case kFailedToWriteToSteamCloud: |
|
ErrorMessage( "Failed to write to Steam cloud!" ); |
|
break; |
|
case kFileNotFound: |
|
ErrorMessage( "File not found!" ); |
|
break; |
|
case kNeedTitleAndDescription: |
|
ErrorMessage( "Need to have a title and description!" ); |
|
break; |
|
case kFailedFileValidation: |
|
ErrorMessage( "File failed to validate!" ); |
|
break; |
|
case kFailedUserModifiedFile: |
|
ErrorMessage( "File was manually modified after verifying process" ); |
|
break; |
|
case kInvalidMapName: |
|
ErrorMessage( "Invalid name for map. Map names must be lowercase and of the form cp_foo.bsp." ); |
|
break; |
|
default: |
|
Assert( false ); // Unhandled enum value |
|
break; |
|
} |
|
if ( pkvTokens ) |
|
{ |
|
pkvTokens->deleteThis(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::ErrorMessage( const char *lpszText ) |
|
{ |
|
vgui::MessageBox *pBox = new vgui::MessageBox( "", lpszText, this ); |
|
pBox->SetPaintBorderEnabled( false ); |
|
pBox->SetPaintBackgroundEnabled( true ); |
|
pBox->SetBgColor( Color(0,0,0,255) ); |
|
pBox->DoModal(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const char* CFilePublishDialog::GetStatusString( StatusCode_t statusCode ) |
|
{ |
|
switch ( statusCode ) |
|
{ |
|
case kPublishing: |
|
return "Publishing, please wait..."; |
|
break; |
|
case kUpdating: |
|
return "Publishing, please wait..."; |
|
break; |
|
} |
|
return ""; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Show our modal status window to cover asynchronous tasks |
|
// TODO: Pull this out into a more generalized solution across dialogs |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::ShowStatusWindow( StatusCode_t statusCode ) |
|
{ |
|
// Throw up our status box |
|
if ( m_pStatusBox ) |
|
{ |
|
m_pStatusBox->CloseModal(); |
|
m_pStatusBox = NULL; // FIXME: Does this clear up the memory? |
|
} |
|
|
|
const char *lpszText = GetStatusString( statusCode ); |
|
|
|
// Pop a message to the user so they know to wait |
|
m_pStatusBox = new vgui::MessageBox( "", lpszText, this ); |
|
m_pStatusBox->SetPaintBorderEnabled( false ); |
|
m_pStatusBox->SetPaintBackgroundEnabled( true ); |
|
m_pStatusBox->SetBgColor( Color(0,0,0,255) ); |
|
m_pStatusBox->SetOKButtonVisible( false ); |
|
m_pStatusBox->DoModal(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Hide our modal status window |
|
// TODO: Pull this out into a more generalized solution across dialogs |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::HideStatusWindow( void ) |
|
{ |
|
m_pStatusBox->CloseModal(); |
|
m_pStatusBox = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::DownloadPreviewImage( void ) |
|
{ |
|
// TODO: We need a generic "no image" image |
|
if ( m_bAddingNewFile ) |
|
return; |
|
|
|
// Start off our download |
|
char szTargetFilename[MAX_PATH]; |
|
V_snprintf( szTargetFilename, sizeof(szTargetFilename), "%llu_thumb.jpg", m_FileDetails.publishedFileDetails.m_nPublishedFileId ); |
|
m_UGCPreviewFileRequest.StartDownload( m_FileDetails.publishedFileDetails.m_hPreviewFile, "downloads", szTargetFilename ); |
|
m_bPreviewDownloadPending = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::OnTick( void ) |
|
{ |
|
BaseClass::OnTick(); |
|
|
|
if ( m_pPrepareFileThread ) |
|
{ |
|
if ( !m_pPrepareFileThread->IsAlive() ) |
|
{ |
|
// Finished, trigger handler |
|
int result = m_pPrepareFileThread->GetResult(); |
|
delete m_pPrepareFileThread; |
|
m_pPrepareFileThread = NULL; |
|
OnFilePrepared( result == 0 ); |
|
} |
|
} |
|
|
|
if ( m_bPreviewDownloadPending ) |
|
{ |
|
UGCFileRequestStatus_t ugcStatus = m_UGCPreviewFileRequest.Update(); |
|
switch ( ugcStatus ) |
|
{ |
|
case UGCFILEREQUEST_ERROR: |
|
Warning("An error occurred while attempting to download a file from the UGC server!\n"); |
|
m_bPreviewDownloadPending = false; |
|
break; |
|
|
|
case UGCFILEREQUEST_FINISHED: |
|
// Update our image preview |
|
char szLocalFilename[MAX_PATH]; |
|
m_UGCPreviewFileRequest.GetLocalFileName( szLocalFilename, sizeof(szLocalFilename) ); |
|
char szLocalPath[ _MAX_PATH ]; |
|
g_pFullFileSystem->GetLocalPath( szLocalFilename, szLocalPath, sizeof(szLocalPath) ); |
|
SetPreviewImage( szLocalPath ); |
|
m_bPreviewDownloadPending = false; |
|
break; |
|
|
|
default: |
|
// Working, continue to wait... |
|
return; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::SetPreviewImage( const char *lpszFilename ) |
|
{ |
|
if ( lpszFilename == NULL ) |
|
return; |
|
|
|
// Retain this |
|
g_PreviewFilename = lpszFilename; |
|
|
|
m_bValidJpeg = false; |
|
|
|
ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( lpszFilename, m_imgSource ); |
|
if ( nErrorCode != CE_SUCCESS ) |
|
{ |
|
} |
|
else |
|
{ |
|
m_bValidJpeg = true; |
|
PerformSquarize(); |
|
m_pCroppedTextureImagePanel->SetBitmap( GetPreviewBitmap() ); |
|
} |
|
|
|
// Update the state of our publish button |
|
SetPublishButtonState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::PerformSquarize() |
|
{ |
|
if ( !BForceSquarePreviewImage() ) |
|
return; |
|
|
|
const Bitmap_t *pResizeSrc = &m_imgSource; |
|
if ( !IsSourceImageSquare() ) |
|
{ |
|
// Select the smaller dimension as the size |
|
int nSize = MIN( m_imgSource.Width(), m_imgSource.Height() ); |
|
|
|
// Crop it. |
|
// Yeah, the crop and resize could be done all in one step. |
|
// And...I don't care. |
|
int x0 = ( m_imgSource.Width() - nSize ) / 2; |
|
int y0 = ( m_imgSource.Height() - nSize ) / 2; |
|
m_imgTemp.Crop( x0, y0, nSize, nSize, &m_imgSource ); |
|
|
|
pResizeSrc = &m_imgTemp; |
|
} |
|
|
|
// resize |
|
ImgUtl_ResizeBitmap( m_imgSquare, DesiredPreviewWidth(), DesiredPreviewHeight(), pResizeSrc ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
Bitmap_t &CFilePublishDialog::GetPreviewBitmap() |
|
{ |
|
return BForceSquarePreviewImage() ? m_imgSquare : m_imgSource; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Setup our edit fields with the appropriate information |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::PopulateEditFields( void ) |
|
{ |
|
m_pFileTitle->SetText( m_FileDetails.publishedFileDetails.m_rgchTitle ); |
|
m_pFileDescription->SetText( m_FileDetails.publishedFileDetails.m_rgchDescription ); |
|
|
|
if ( m_FileDetails.lpszFilename && !FStrEq( m_FileDetails.lpszFilename, "" ) ) |
|
{ |
|
char szShortName[ MAX_PATH ]; |
|
Q_FileBase( m_FileDetails.lpszFilename, szShortName, sizeof(szShortName) ); |
|
const char *szExt = Q_GetFileExtension( m_FileDetails.lpszFilename ); |
|
Q_SetExtension( szShortName, CFmtStr( ".%s", szExt ).Access(), sizeof(szShortName ) ); |
|
m_pFilename->SetText( szShortName ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
|
|
LoadControlSettings( GetResFile() ); |
|
|
|
m_pFileTitle = dynamic_cast< vgui::TextEntry * >( FindChildByName( "FileTitle" ) ); |
|
if ( m_pFileTitle ) |
|
{ |
|
m_pFileTitle->AddActionSignalTarget( this ); |
|
} |
|
|
|
m_pFileDescription = dynamic_cast< vgui::TextEntry * >( FindChildByName( "FileDesc" ) ); |
|
if ( m_pFileDescription ) |
|
{ |
|
m_pFileDescription->SetMultiline( true ); |
|
m_pFileDescription->SetCatchEnterKey( true ); |
|
m_pFileDescription->SetVerticalScrollbar( true ); |
|
} |
|
|
|
m_pFilename = dynamic_cast< vgui::Label * >( FindChildByName( "SourceFile" ) ); |
|
if ( !g_MapFilename.IsEmpty() ) |
|
{ |
|
m_pFilename->SetText( g_MapFilename ); |
|
} |
|
|
|
m_pPublishButton = dynamic_cast< vgui::Button * >( FindChildByName( "ButtonPublish" ) ); |
|
|
|
// If we're updating, change the context of the button |
|
if ( !m_bAddingNewFile ) |
|
{ |
|
m_pPublishButton->SetText( "Update" ); |
|
m_pPublishButton->SetCommand( "Update" ); |
|
} |
|
|
|
// Setup our initial state for the edit fields |
|
PopulateEditFields(); |
|
SetPublishButtonState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper to build thumbnail name |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::GetPreviewFilename( char *szOut, size_t outLen ) |
|
{ |
|
char szMapShortName[MAX_PATH]; |
|
Q_FileBase( g_MapFilename, szMapShortName, sizeof(szMapShortName) ); |
|
Q_snprintf( szOut, outLen, "%s_thumb.jpg", szMapShortName ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Callback when our create item has completed. Need to do initial update. |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::Steam_OnCreateItem( CreateItemResult_t *pResult, bool bError ) |
|
{ |
|
bError = bError || pResult->m_eResult != k_EResultOK; |
|
m_nFileID = pResult->m_nPublishedFileId; |
|
|
|
if ( bError ) |
|
{ |
|
HideStatusWindow(); |
|
ErrorMessage( kFailedToPublishFile ); |
|
if ( m_nFileID != k_PublishedFileIdInvalid ) |
|
{ |
|
// TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage. |
|
// Once this is fixed in steam, this call should probably be moved |
|
steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID ); |
|
m_nFileID = k_PublishedFileIdInvalid; |
|
} |
|
} |
|
else |
|
{ |
|
StartPrepareFile(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Callback from our map compression thread finishing |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::OnFilePrepared( bool bSucceeded ) |
|
{ |
|
if ( bSucceeded ) |
|
{ |
|
// Move on to final publishing |
|
bSucceeded = UpdateFileInternal(); |
|
} |
|
|
|
if ( bSucceeded ) |
|
{ |
|
// Done, waiting on file publish callback |
|
return; |
|
} |
|
|
|
// Failure |
|
|
|
// This is after OnCreateItem for new files, so cleanup the incomplete item on failure from either the compress or |
|
// kicking off the update. |
|
if ( m_bAddingNewFile && m_nFileID != k_PublishedFileIdInvalid ) |
|
{ |
|
// TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage. |
|
// Once this is fixed in steam, this call should probably be moved |
|
steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID ); |
|
m_nFileID = k_PublishedFileIdInvalid; |
|
} |
|
|
|
HideStatusWindow(); |
|
ErrorMessage( kFailedToUpdateFile ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Callback when our publish call has completed |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::Steam_OnPublishFile( SubmitItemUpdateResult_t *pResult, bool bError ) |
|
{ |
|
// Remove prepared map |
|
char szPreparedMap[MAX_PATH] = { 0 }; |
|
V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ), |
|
szPreparedMap, sizeof( szPreparedMap ) ); |
|
g_pFullFileSystem->RemoveFile( szPreparedMap, UGC_PATHID ); |
|
|
|
// Remove local thumbnail |
|
CUtlBuffer bufData; |
|
char szPreviewFilename[MAX_PATH]; |
|
GetPreviewFilename( szPreviewFilename, sizeof( szPreviewFilename ) ); |
|
g_pFullFileSystem->RemoveFile( szPreviewFilename, UGC_PATHID ); |
|
|
|
HideStatusWindow(); |
|
|
|
if ( bError || pResult->m_eResult != k_EResultOK ) |
|
{ |
|
if ( m_bAddingNewFile && m_nFileID != k_PublishedFileIdInvalid ) |
|
{ |
|
// TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage. |
|
// Once this is fixed in steam, this call should probably be moved |
|
steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID ); |
|
m_nFileID = k_PublishedFileIdInvalid; |
|
} |
|
ErrorMessage( kFailedToPublishFile ); |
|
} |
|
else |
|
{ |
|
EUniverse universe = GetUniverse(); |
|
switch ( universe ) |
|
{ |
|
case k_EUniversePublic: |
|
steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://steamcommunity.com/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) ); |
|
break; |
|
case k_EUniverseBeta: |
|
steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://beta.steamcommunity.com/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) ); |
|
break; |
|
case k_EUniverseDev: |
|
steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://localhost/community/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) ); |
|
break; |
|
} |
|
|
|
// Tell our parent what happened |
|
KeyValues *pkvActionSignal = new KeyValues( "ChangedFile" ); |
|
pkvActionSignal->SetUint64( "nPublishedFileID", m_nFileID ); |
|
PostActionSignal( pkvActionSignal ); |
|
|
|
// Close down the window |
|
CloseModal(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Share the file with Steam Cloud and return the handle for later usage |
|
//----------------------------------------------------------------------------- |
|
bool CFilePublishDialog::PublishFile() |
|
{ |
|
// Must be a valid file |
|
ErrorCode_t errorCode = ValidateFile( g_MapFilename ); |
|
#ifdef TF_CLIENT_DLL |
|
const char *pExt = V_GetFileExtension( g_MapFilename ); |
|
if ( errorCode == kNoError && pExt && V_strcmp( pExt, "bsp" ) == 0 ) |
|
{ |
|
if ( !CTFMapsWorkshop::IsValidOriginalFileNameForMap( CUtlString( V_GetFileName( g_MapFilename ) ) ) ) |
|
{ |
|
errorCode = kInvalidMapName; |
|
} |
|
} |
|
#endif |
|
if ( errorCode != kNoError ) |
|
{ |
|
ErrorMessage( errorCode ); |
|
return false; |
|
} |
|
|
|
ShowStatusWindow( kPublishing ); |
|
|
|
EWorkshopFileType eFileType = WorkshipFileTypeForFile( g_MapFilename ); |
|
|
|
// Create file on UGC |
|
SteamAPICall_t hSteamAPICall = steamapicontext->SteamUGC()->CreateItem( GetTargetAppID(), eFileType ); |
|
|
|
// Set the callback |
|
m_callbackCreateItem.Set( hSteamAPICall, this, &CFilePublishDialog::Steam_OnCreateItem ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Kick off the map compression thread |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::StartPrepareFile( void ) |
|
{ |
|
// Ensure temp dir exists |
|
g_pFullFileSystem->CreateDirHierarchy( WORKSHOP_TEMP_UPLOAD_DIR, UGC_PATHID ); |
|
|
|
char szOutPath[MAX_PATH] = { 0 }; |
|
V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ), |
|
szOutPath, sizeof( szOutPath ) ); |
|
|
|
// Ensure this file isn't leftover in output dir |
|
g_pFullFileSystem->RemoveFile( szOutPath, UGC_PATHID ); |
|
|
|
// Start thread |
|
Assert( !m_pPrepareFileThread ); |
|
m_pPrepareFileThread = new CPrepareFileThread( g_MapFilename, szOutPath ); |
|
m_pPrepareFileThread->Start(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse commands coming in from the VGUI dialog |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::SetPublishButtonState( void ) |
|
{ |
|
if ( m_bAddingNewFile ) |
|
{ |
|
if ( m_bValidFile && m_bValidJpeg ) |
|
{ |
|
m_pPublishButton->SetEnabled( true ); |
|
} |
|
else |
|
{ |
|
m_pPublishButton->SetEnabled( false ); |
|
} |
|
} |
|
else // Updating a previous entry |
|
{ |
|
// m_pPublishButton->SetEnabled( (m_nFileDetailsChanges!=0) ); |
|
m_pPublishButton->SetEnabled( true ); // For now, always allow it. Worst case it's a no-op |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse commands coming in from the VGUI dialog |
|
//----------------------------------------------------------------------------- |
|
bool CFilePublishDialog::UpdateFile( void ) |
|
{ |
|
// We should have been created for an existing file or published already, both of which set our ID. |
|
Assert( m_nFileID != k_PublishedFileIdInvalid ); |
|
ShowStatusWindow( kUpdating ); |
|
|
|
if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE ) |
|
{ |
|
StartPrepareFile(); |
|
} |
|
else |
|
{ |
|
// Not updating map, go straight to update step |
|
if ( !UpdateFileInternal() ) |
|
{ |
|
HideStatusWindow(); |
|
ErrorMessage( kFailedToUpdateFile ); |
|
} |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update a file, used by the create and update pathways to fill in a UGC item |
|
//----------------------------------------------------------------------------- |
|
bool CFilePublishDialog::UpdateFileInternal() |
|
{ |
|
ISteamUGC *pUGC = steamapicontext->SteamUGC(); |
|
|
|
UGCUpdateHandle_t hItem = pUGC->StartItemUpdate( GetTargetAppID(), m_nFileID ); |
|
if ( hItem == k_UGCUpdateHandleInvalid ) |
|
{ |
|
UGCWarning( "StartItemUpdate failed\n" ); |
|
return false; |
|
} |
|
|
|
bool bError = false; |
|
|
|
// create thumbnail |
|
CUtlBuffer bufData; |
|
char szPreviewFilename[MAX_PATH]; |
|
GetPreviewFilename( szPreviewFilename, sizeof( szPreviewFilename ) ); |
|
|
|
if ( !bError && ImgUtl_SaveBitmapToBuffer( bufData, GetPreviewBitmap(), kImageFileFormat_JPG ) == CE_SUCCESS ) |
|
{ |
|
bError = !g_pFullFileSystem->WriteFile( szPreviewFilename, UGC_PATHID, bufData ); |
|
|
|
// Get full path to give steam |
|
g_pFullFileSystem->RelativePathToFullPath( szPreviewFilename, UGC_PATHID, szPreviewFilename, sizeof( szPreviewFilename ) ); |
|
} |
|
else |
|
{ |
|
bError = true; |
|
} |
|
|
|
// Get the compressed map out of the upload directory |
|
char szPreparedMap[MAX_PATH] = { 0 }; |
|
char szFullPreparedPath[MAX_PATH] = { 0 }; |
|
if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE ) |
|
{ |
|
V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ), |
|
szPreparedMap, sizeof( szPreparedMap ) ); |
|
|
|
g_pFullFileSystem->RelativePathToFullPath( szPreparedMap, UGC_PATHID, |
|
szFullPreparedPath, |
|
sizeof( szFullPreparedPath ) ); |
|
|
|
bError |= !*szFullPreparedPath; |
|
} |
|
|
|
if ( !bError ) |
|
{ |
|
// Set title |
|
char szTitle[k_cchPublishedDocumentTitleMax]; |
|
m_pFileTitle->GetText( szTitle, sizeof(szTitle) ); |
|
Q_AggressiveStripPrecedingAndTrailingWhitespace( szTitle ); |
|
|
|
bError |= !pUGC->SetItemTitle( hItem, szTitle ); |
|
|
|
// Set descriptor |
|
char szDesc[k_cchPublishedDocumentDescriptionMax]; |
|
m_pFileDescription->GetText( szDesc, sizeof(szDesc) ); |
|
Q_AggressiveStripPrecedingAndTrailingWhitespace( szDesc ); |
|
|
|
bError |= !pUGC->SetItemDescription( hItem, szDesc ); |
|
|
|
// Set thumbnail |
|
if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_PREVIEW ) |
|
{ |
|
bError |= !pUGC->SetItemPreview( hItem, szPreviewFilename ); |
|
} |
|
|
|
// Set file |
|
if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE ) |
|
{ |
|
if ( *szFullPreparedPath ) |
|
{ |
|
bError |= !pUGC->SetItemContent( hItem, szFullPreparedPath ); |
|
// Metadata for our files is just the original filename, since they are currently all single files |
|
bError |= !pUGC->SetItemMetadata( hItem, V_GetFileName( g_MapFilename.Get() ) ); |
|
} |
|
else |
|
{ |
|
UGCWarning( "Prepared map does not appear to exist\n" ); |
|
bError = true; |
|
} |
|
} |
|
|
|
// Tags |
|
SteamParamStringArray_t strArray; |
|
PopulateTags( strArray ); |
|
bError |= !pUGC->SetItemTags( hItem, &strArray ); |
|
|
|
// Visibility |
|
bError |= !pUGC->SetItemVisibility( hItem, k_ERemoteStoragePublishedFileVisibilityPublic ); |
|
} |
|
else |
|
{ |
|
bError = true; |
|
} |
|
|
|
if ( !bError ) |
|
{ |
|
SteamAPICall_t hSteamAPICall = steamapicontext->SteamUGC()->SubmitItemUpdate( hItem, NULL ); |
|
m_callbackPublishFile.Set( hSteamAPICall, this, &CFilePublishDialog::Steam_OnPublishFile ); |
|
return true; |
|
} |
|
|
|
// Failed, cleanup prepared map |
|
g_pFullFileSystem->RemoveFile( szPreparedMap, UGC_PATHID ); |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::PerformLayout() |
|
{ |
|
BaseClass::PerformLayout(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse commands coming in from the VGUI dialog |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::OnCommand( const char *command ) |
|
{ |
|
if ( Q_stricmp( command, "Publish" ) == 0 ) |
|
{ |
|
// Verify they've filled everything out properly |
|
bool bHasTitle = ( m_pFileTitle->GetTextLength() > 0 ); |
|
bool bHasDesc = ( m_pFileDescription->GetTextLength() > 0 ); |
|
if ( !bHasTitle || !bHasDesc ) |
|
{ |
|
ErrorMessage( kNeedTitleAndDescription ); |
|
return; |
|
} |
|
|
|
// Get our title |
|
char szTitle[k_cchPublishedDocumentTitleMax]; |
|
m_pFileTitle->GetText( szTitle, sizeof(szTitle) ); |
|
Q_AggressiveStripPrecedingAndTrailingWhitespace( szTitle ); |
|
|
|
// Get our descriptor |
|
char szDesc[k_cchPublishedDocumentDescriptionMax]; |
|
m_pFileDescription->GetText( szDesc, sizeof(szDesc) ); |
|
Q_AggressiveStripPrecedingAndTrailingWhitespace( szDesc ); |
|
|
|
bHasTitle = Q_strlen( szTitle ) != 0; |
|
bHasDesc = Q_strlen( szDesc ) != 0; |
|
if ( !bHasTitle || !bHasDesc ) |
|
{ |
|
ErrorMessage( kNeedTitleAndDescription ); |
|
return; |
|
} |
|
|
|
PublishFile(); |
|
} |
|
else if ( Q_stricmp( command, "Update" ) == 0 ) |
|
{ |
|
UpdateFile(); |
|
} |
|
else if ( Q_stricmp( command, "MainFileMaps" ) == 0 ) |
|
{ |
|
m_fileOpenMode = FILEOPEN_MAIN_FILE; |
|
|
|
// Create a new dialog |
|
vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true ); |
|
pDlg->AddFilter( GetFileTypes( IMPORT_FILTER_MAP ), GetFileTypeDescriptions( IMPORT_FILTER_MAP ), true ); |
|
if ( !FStrEq( publish_file_last_dir.GetString(), "" ) ) |
|
{ |
|
pDlg->SetStartDirectory( publish_file_last_dir.GetString() ); |
|
} |
|
|
|
char textBuffer[1024]; |
|
m_pFilename->GetText( textBuffer, sizeof( textBuffer ) ); |
|
|
|
char szFilePath[MAX_PATH]; |
|
g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof(szFilePath) ); |
|
|
|
strcat( szFilePath, "/" ); |
|
strcat( szFilePath, textBuffer ); |
|
|
|
// Get the currently set dir and use that as the start |
|
// pDlg->ExpandTreeToPath( szFilePath ); |
|
pDlg->MoveToCenterOfScreen(); |
|
pDlg->AddActionSignalTarget( this ); |
|
pDlg->SetDeleteSelfOnClose( true ); |
|
pDlg->DoModal(); |
|
pDlg->Activate(); |
|
} |
|
else if ( Q_stricmp( command, "MainFileOther" ) == 0 ) |
|
{ |
|
m_fileOpenMode = FILEOPEN_MAIN_FILE; |
|
|
|
// Create a new dialog |
|
vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true ); |
|
pDlg->AddFilter( GetFileTypes( IMPORT_FILTER_OTHER ), GetFileTypeDescriptions( IMPORT_FILTER_OTHER ), true ); |
|
if ( !FStrEq( publish_file_last_dir.GetString(), "" ) ) |
|
{ |
|
pDlg->SetStartDirectory( publish_file_last_dir.GetString() ); |
|
} |
|
|
|
char textBuffer[1024]; |
|
m_pFilename->GetText( textBuffer, sizeof( textBuffer ) ); |
|
|
|
char szFilePath[MAX_PATH]; |
|
g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof( szFilePath ) ); |
|
|
|
strcat( szFilePath, "/" ); |
|
strcat( szFilePath, textBuffer ); |
|
|
|
// Get the currently set dir and use that as the start |
|
// pDlg->ExpandTreeToPath( szFilePath ); |
|
pDlg->MoveToCenterOfScreen(); |
|
pDlg->AddActionSignalTarget( this ); |
|
pDlg->SetDeleteSelfOnClose( true ); |
|
pDlg->DoModal(); |
|
pDlg->Activate(); |
|
} |
|
else if ( Q_stricmp( command, "PreviewBrowse" ) == 0 ) |
|
{ |
|
m_fileOpenMode = FILEOPEN_PREVIEW; |
|
|
|
// Create a new dialog |
|
vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true ); |
|
pDlg->AddFilter( GetPreviewFileTypes(), GetPreviewFileTypeDescriptions(), true ); |
|
if ( !FStrEq( publish_file_last_dir.GetString(), "" ) ) |
|
{ |
|
pDlg->SetStartDirectory( publish_file_last_dir.GetString() ); |
|
} |
|
|
|
char szFilePath[MAX_PATH]; |
|
g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof(szFilePath) ); |
|
|
|
strcat( szFilePath, "/" ); |
|
strcat( szFilePath, g_PreviewFilename ); |
|
|
|
// Get the currently set dir and use that as the start |
|
// pDlg->ExpandTreeToPath( szFilePath ); |
|
pDlg->MoveToCenterOfScreen(); |
|
pDlg->AddActionSignalTarget( this ); |
|
pDlg->SetDeleteSelfOnClose( true ); |
|
pDlg->DoModal(); |
|
pDlg->Activate(); |
|
} |
|
else |
|
{ |
|
BaseClass::OnCommand( command ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Take a filename, shorten it for display but retain the full path internally |
|
//----------------------------------------------------------------------------- |
|
CFilePublishDialog::ErrorCode_t CFilePublishDialog::ValidateFile( const char *lpszFilename ) |
|
{ |
|
return kNoError; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Take a filename, shorten it for display but retain the full path internally |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::SetFile( const char *lpszFilename, bool bImported ) |
|
{ |
|
// Must be a valid file |
|
ErrorCode_t errorCode = ValidateFile( lpszFilename ); |
|
if ( errorCode != kNoError ) |
|
{ |
|
ErrorMessage( errorCode ); |
|
return; |
|
} |
|
|
|
m_bValidFile = true; |
|
g_MapFilename = lpszFilename; |
|
char szShortName[ MAX_PATH ]; |
|
Q_FileBase( g_MapFilename, szShortName, sizeof(szShortName) ); |
|
const char *szExt = Q_GetFileExtension( lpszFilename ); |
|
Q_SetExtension( szShortName, CFmtStr( ".%s", szExt ).Access(), sizeof(szShortName ) ); |
|
m_pFilename->SetText( szShortName ); |
|
|
|
// Notify of the change |
|
m_nFileDetailsChanges |= PFILE_FIELD_FILE; |
|
|
|
SetPublishButtonState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Notify us that the directory dialog has returned a new entry |
|
//----------------------------------------------------------------------------- |
|
void CFilePublishDialog::OnFileSelected( const char *fullPath ) |
|
{ |
|
char basepath[ MAX_PATH ]; |
|
Q_ExtractFilePath( fullPath, basepath, sizeof( basepath ) ); |
|
publish_file_last_dir.SetValue( basepath ); |
|
|
|
if ( m_fileOpenMode == FILEOPEN_MAIN_FILE ) |
|
{ |
|
SetFile( fullPath ); |
|
} |
|
else if ( m_fileOpenMode == FILEOPEN_PREVIEW ) |
|
{ |
|
// Notify of the change |
|
m_nFileDetailsChanges |= PFILE_FIELD_PREVIEW; |
|
|
|
SetPreviewImage( fullPath ); |
|
} |
|
}
|
|
|