summaryrefslogtreecommitdiff
path: root/game/client/steampublishedfiles/publish_file_dialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/steampublishedfiles/publish_file_dialog.cpp')
-rw-r--r--game/client/steampublishedfiles/publish_file_dialog.cpp985
1 files changed, 985 insertions, 0 deletions
diff --git a/game/client/steampublishedfiles/publish_file_dialog.cpp b/game/client/steampublishedfiles/publish_file_dialog.cpp
new file mode 100644
index 0000000..e64cd67
--- /dev/null
+++ b/game/client/steampublishedfiles/publish_file_dialog.cpp
@@ -0,0 +1,985 @@
+//========= 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 );
+ }
+}