Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Dialog for selecting game configurations
  4. //
  5. //=====================================================================================//
  6. #include "cbase.h"
  7. #include <vgui/IVGui.h>
  8. #include <vgui/IInput.h>
  9. #include <vgui/ISystem.h>
  10. #include <vgui/ISurface.h>
  11. #include <vgui_controls/Button.h>
  12. #include <vgui_controls/TextEntry.h>
  13. #include <vgui_controls/MessageBox.h>
  14. #include <vgui_controls/ImagePanel.h>
  15. #include <vgui_controls/FileOpenDialog.h>
  16. #include "vgui_bitmappanel.h"
  17. #include <KeyValues.h>
  18. #include "imageutils.h"
  19. #include "bsp_utils.h"
  20. #include "icommandline.h"
  21. #include "publish_file_dialog.h"
  22. #include "workshop/ugc_utils.h"
  23. #ifdef TF_CLIENT_DLL
  24. #include "../server/tf/workshop/maps_workshop.h"
  25. #endif // TF_CLIENT_DLL
  26. #include "steam/steam_api.h"
  27. // memdbgon must be the last include file in a .cpp file!!!
  28. #include <tier0/memdbgon.h>
  29. static CUtlString g_MapFilename;
  30. static CUtlString g_PreviewFilename;
  31. ConVar publish_file_last_dir( "publish_file_last_dir", "", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD );
  32. CFilePublishDialog *g_pSteamFilePublishDialog = NULL;
  33. #define WORKSHOP_TEMP_UPLOAD_DIR "workshop/upload"
  34. class CPrepareFileThread : public CThread
  35. {
  36. public:
  37. CPrepareFileThread( const char *pszInputFile, const char *pszOutputFile )
  38. : m_strInput( pszInputFile )
  39. , m_strOutput( pszOutputFile )
  40. {}
  41. // Return 0 for success
  42. virtual int Run()
  43. {
  44. if ( V_strcasecmp( V_GetFileExtension( m_strInput.Get() ), "bsp" ) == 0 )
  45. {
  46. return BSP_SyncRepack( m_strInput.Get(), m_strOutput.Get() ) ? 0 : 1;
  47. }
  48. else
  49. {
  50. // Just copy file to prepared location
  51. return engine->CopyLocalFile( m_strInput.Get(), m_strOutput.Get() ) ? 0 : 1;
  52. }
  53. }
  54. private:
  55. CUtlString m_strInput;
  56. CUtlString m_strOutput;
  57. };
  58. //-----------------------------------------------------------------------------
  59. // Constructor
  60. //-----------------------------------------------------------------------------
  61. CFilePublishDialog::CFilePublishDialog( Panel *parent, const char *name, PublishedFileDetails_t *pDetails ) : BaseClass( parent, name )
  62. {
  63. m_pPrepareFileThread = NULL;
  64. g_pSteamFilePublishDialog = this;
  65. // These must be supplied
  66. m_bValidFile = false;
  67. m_bValidJpeg = false;
  68. vgui::ivgui()->AddTickSignal( GetVPanel(), 0 );
  69. // Save this for later
  70. if ( pDetails != NULL )
  71. {
  72. m_FileDetails = *pDetails;
  73. m_bAddingNewFile = false;
  74. g_MapFilename = m_FileDetails.lpszFilename;
  75. m_nFileID = m_FileDetails.publishedFileDetails.m_nPublishedFileId;
  76. }
  77. else
  78. {
  79. // Clear it out
  80. m_FileDetails.lpszFilename = NULL;
  81. m_FileDetails.publishedFileDetails.m_eVisibility = k_ERemoteStoragePublishedFileVisibilityPublic;
  82. m_FileDetails.publishedFileDetails.m_hFile = k_UGCHandleInvalid;
  83. m_FileDetails.publishedFileDetails.m_hPreviewFile = k_UGCHandleInvalid;
  84. m_FileDetails.publishedFileDetails.m_nConsumerAppID = k_uAppIdInvalid;
  85. m_FileDetails.publishedFileDetails.m_nCreatorAppID = k_uAppIdInvalid;
  86. m_FileDetails.publishedFileDetails.m_nPublishedFileId = 0; // FIXME: Need a real "invalid" value
  87. m_FileDetails.publishedFileDetails.m_rtimeCreated = 0;
  88. m_FileDetails.publishedFileDetails.m_rtimeUpdated = 0;
  89. m_FileDetails.publishedFileDetails.m_ulSteamIDOwner = 0; // FIXME: Need a real "invalid" value
  90. memset( m_FileDetails.publishedFileDetails.m_rgchDescription, 0, k_cchPublishedDocumentDescriptionMax );
  91. memset( m_FileDetails.publishedFileDetails.m_rgchTitle, 0, k_cchPublishedDocumentTitleMax );
  92. m_bAddingNewFile = true;
  93. g_MapFilename = "";
  94. m_nFileID = k_PublishedFileIdInvalid;
  95. }
  96. m_nFileDetailsChanges = 0;
  97. m_fileOpenMode = FILEOPEN_NONE;
  98. // Setup our image panel
  99. m_pCroppedTextureImagePanel = new CBitmapPanel( this, "PreviewImage" );
  100. m_pCroppedTextureImagePanel->SetSize( DesiredPreviewWidth(), DesiredPreviewHeight() );
  101. m_pCroppedTextureImagePanel->SetVisible( true );
  102. m_pStatusBox = NULL;
  103. // Start downloading our preview image
  104. m_bPreviewDownloadPending = false;
  105. DownloadPreviewImage();
  106. }
  107. //-----------------------------------------------------------------------------
  108. // Destructor
  109. //-----------------------------------------------------------------------------
  110. CFilePublishDialog::~CFilePublishDialog()
  111. {
  112. //delete m_pConfigCombo;
  113. g_pSteamFilePublishDialog = NULL;
  114. // We should be in a modal dialog when this is running, not closable
  115. Assert( !m_pPrepareFileThread );
  116. if ( m_pPrepareFileThread )
  117. {
  118. m_pPrepareFileThread->Stop();
  119. delete m_pPrepareFileThread;
  120. m_pPrepareFileThread = NULL;
  121. }
  122. }
  123. void CFilePublishDialog::ErrorMessage( ErrorCode_t errorCode, KeyValues *pkvTokens )
  124. {
  125. switch ( errorCode )
  126. {
  127. case kFailedToPublishFile:
  128. ErrorMessage( "Failed to publish file!" );
  129. break;
  130. case kFailedToUpdateFile:
  131. ErrorMessage( "Failed to update file!" );
  132. break;
  133. case kFailedToPrepareFile:
  134. ErrorMessage( "Failed to prepare file!" );
  135. break;
  136. case kSteamCloudNotAvailable:
  137. ErrorMessage( "Steam Cloud is not available." );
  138. break;
  139. case kSteamExceededCloudQuota:
  140. ErrorMessage( "Exceed Steam Cloud quota." );
  141. break;
  142. case kFailedToWriteToSteamCloud:
  143. ErrorMessage( "Failed to write to Steam cloud!" );
  144. break;
  145. case kFileNotFound:
  146. ErrorMessage( "File not found!" );
  147. break;
  148. case kNeedTitleAndDescription:
  149. ErrorMessage( "Need to have a title and description!" );
  150. break;
  151. case kFailedFileValidation:
  152. ErrorMessage( "File failed to validate!" );
  153. break;
  154. case kFailedUserModifiedFile:
  155. ErrorMessage( "File was manually modified after verifying process" );
  156. break;
  157. case kInvalidMapName:
  158. ErrorMessage( "Invalid name for map. Map names must be lowercase and of the form cp_foo.bsp." );
  159. break;
  160. default:
  161. Assert( false ); // Unhandled enum value
  162. break;
  163. }
  164. if ( pkvTokens )
  165. {
  166. pkvTokens->deleteThis();
  167. }
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Purpose:
  171. //-----------------------------------------------------------------------------
  172. void CFilePublishDialog::ErrorMessage( const char *lpszText )
  173. {
  174. vgui::MessageBox *pBox = new vgui::MessageBox( "", lpszText, this );
  175. pBox->SetPaintBorderEnabled( false );
  176. pBox->SetPaintBackgroundEnabled( true );
  177. pBox->SetBgColor( Color(0,0,0,255) );
  178. pBox->DoModal();
  179. }
  180. //-----------------------------------------------------------------------------
  181. // Purpose:
  182. //-----------------------------------------------------------------------------
  183. const char* CFilePublishDialog::GetStatusString( StatusCode_t statusCode )
  184. {
  185. switch ( statusCode )
  186. {
  187. case kPublishing:
  188. return "Publishing, please wait...";
  189. break;
  190. case kUpdating:
  191. return "Publishing, please wait...";
  192. break;
  193. }
  194. return "";
  195. }
  196. //-----------------------------------------------------------------------------
  197. // Purpose: Show our modal status window to cover asynchronous tasks
  198. // TODO: Pull this out into a more generalized solution across dialogs
  199. //-----------------------------------------------------------------------------
  200. void CFilePublishDialog::ShowStatusWindow( StatusCode_t statusCode )
  201. {
  202. // Throw up our status box
  203. if ( m_pStatusBox )
  204. {
  205. m_pStatusBox->CloseModal();
  206. m_pStatusBox = NULL; // FIXME: Does this clear up the memory?
  207. }
  208. const char *lpszText = GetStatusString( statusCode );
  209. // Pop a message to the user so they know to wait
  210. m_pStatusBox = new vgui::MessageBox( "", lpszText, this );
  211. m_pStatusBox->SetPaintBorderEnabled( false );
  212. m_pStatusBox->SetPaintBackgroundEnabled( true );
  213. m_pStatusBox->SetBgColor( Color(0,0,0,255) );
  214. m_pStatusBox->SetOKButtonVisible( false );
  215. m_pStatusBox->DoModal();
  216. }
  217. //-----------------------------------------------------------------------------
  218. // Purpose: Hide our modal status window
  219. // TODO: Pull this out into a more generalized solution across dialogs
  220. //-----------------------------------------------------------------------------
  221. void CFilePublishDialog::HideStatusWindow( void )
  222. {
  223. m_pStatusBox->CloseModal();
  224. m_pStatusBox = NULL;
  225. }
  226. //-----------------------------------------------------------------------------
  227. // Purpose:
  228. //-----------------------------------------------------------------------------
  229. void CFilePublishDialog::DownloadPreviewImage( void )
  230. {
  231. // TODO: We need a generic "no image" image
  232. if ( m_bAddingNewFile )
  233. return;
  234. // Start off our download
  235. char szTargetFilename[MAX_PATH];
  236. V_snprintf( szTargetFilename, sizeof(szTargetFilename), "%llu_thumb.jpg", m_FileDetails.publishedFileDetails.m_nPublishedFileId );
  237. m_UGCPreviewFileRequest.StartDownload( m_FileDetails.publishedFileDetails.m_hPreviewFile, "downloads", szTargetFilename );
  238. m_bPreviewDownloadPending = true;
  239. }
  240. //-----------------------------------------------------------------------------
  241. // Purpose:
  242. //-----------------------------------------------------------------------------
  243. void CFilePublishDialog::OnTick( void )
  244. {
  245. BaseClass::OnTick();
  246. if ( m_pPrepareFileThread )
  247. {
  248. if ( !m_pPrepareFileThread->IsAlive() )
  249. {
  250. // Finished, trigger handler
  251. int result = m_pPrepareFileThread->GetResult();
  252. delete m_pPrepareFileThread;
  253. m_pPrepareFileThread = NULL;
  254. OnFilePrepared( result == 0 );
  255. }
  256. }
  257. if ( m_bPreviewDownloadPending )
  258. {
  259. UGCFileRequestStatus_t ugcStatus = m_UGCPreviewFileRequest.Update();
  260. switch ( ugcStatus )
  261. {
  262. case UGCFILEREQUEST_ERROR:
  263. Warning("An error occurred while attempting to download a file from the UGC server!\n");
  264. m_bPreviewDownloadPending = false;
  265. break;
  266. case UGCFILEREQUEST_FINISHED:
  267. // Update our image preview
  268. char szLocalFilename[MAX_PATH];
  269. m_UGCPreviewFileRequest.GetLocalFileName( szLocalFilename, sizeof(szLocalFilename) );
  270. char szLocalPath[ _MAX_PATH ];
  271. g_pFullFileSystem->GetLocalPath( szLocalFilename, szLocalPath, sizeof(szLocalPath) );
  272. SetPreviewImage( szLocalPath );
  273. m_bPreviewDownloadPending = false;
  274. break;
  275. default:
  276. // Working, continue to wait...
  277. return;
  278. break;
  279. }
  280. }
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose:
  284. //-----------------------------------------------------------------------------
  285. void CFilePublishDialog::SetPreviewImage( const char *lpszFilename )
  286. {
  287. if ( lpszFilename == NULL )
  288. return;
  289. // Retain this
  290. g_PreviewFilename = lpszFilename;
  291. m_bValidJpeg = false;
  292. ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( lpszFilename, m_imgSource );
  293. if ( nErrorCode != CE_SUCCESS )
  294. {
  295. }
  296. else
  297. {
  298. m_bValidJpeg = true;
  299. PerformSquarize();
  300. m_pCroppedTextureImagePanel->SetBitmap( GetPreviewBitmap() );
  301. }
  302. // Update the state of our publish button
  303. SetPublishButtonState();
  304. }
  305. //-----------------------------------------------------------------------------
  306. // Purpose:
  307. //-----------------------------------------------------------------------------
  308. void CFilePublishDialog::PerformSquarize()
  309. {
  310. if ( !BForceSquarePreviewImage() )
  311. return;
  312. const Bitmap_t *pResizeSrc = &m_imgSource;
  313. if ( !IsSourceImageSquare() )
  314. {
  315. // Select the smaller dimension as the size
  316. int nSize = MIN( m_imgSource.Width(), m_imgSource.Height() );
  317. // Crop it.
  318. // Yeah, the crop and resize could be done all in one step.
  319. // And...I don't care.
  320. int x0 = ( m_imgSource.Width() - nSize ) / 2;
  321. int y0 = ( m_imgSource.Height() - nSize ) / 2;
  322. m_imgTemp.Crop( x0, y0, nSize, nSize, &m_imgSource );
  323. pResizeSrc = &m_imgTemp;
  324. }
  325. // resize
  326. ImgUtl_ResizeBitmap( m_imgSquare, DesiredPreviewWidth(), DesiredPreviewHeight(), pResizeSrc );
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose:
  330. //-----------------------------------------------------------------------------
  331. Bitmap_t &CFilePublishDialog::GetPreviewBitmap()
  332. {
  333. return BForceSquarePreviewImage() ? m_imgSquare : m_imgSource;
  334. }
  335. //-----------------------------------------------------------------------------
  336. // Purpose: Setup our edit fields with the appropriate information
  337. //-----------------------------------------------------------------------------
  338. void CFilePublishDialog::PopulateEditFields( void )
  339. {
  340. m_pFileTitle->SetText( m_FileDetails.publishedFileDetails.m_rgchTitle );
  341. m_pFileDescription->SetText( m_FileDetails.publishedFileDetails.m_rgchDescription );
  342. if ( m_FileDetails.lpszFilename && !FStrEq( m_FileDetails.lpszFilename, "" ) )
  343. {
  344. char szShortName[ MAX_PATH ];
  345. Q_FileBase( m_FileDetails.lpszFilename, szShortName, sizeof(szShortName) );
  346. const char *szExt = Q_GetFileExtension( m_FileDetails.lpszFilename );
  347. Q_SetExtension( szShortName, CFmtStr( ".%s", szExt ).Access(), sizeof(szShortName ) );
  348. m_pFilename->SetText( szShortName );
  349. }
  350. }
  351. //-----------------------------------------------------------------------------
  352. // Purpose:
  353. //-----------------------------------------------------------------------------
  354. void CFilePublishDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  355. {
  356. BaseClass::ApplySchemeSettings( pScheme );
  357. LoadControlSettings( GetResFile() );
  358. m_pFileTitle = dynamic_cast< vgui::TextEntry * >( FindChildByName( "FileTitle" ) );
  359. if ( m_pFileTitle )
  360. {
  361. m_pFileTitle->AddActionSignalTarget( this );
  362. }
  363. m_pFileDescription = dynamic_cast< vgui::TextEntry * >( FindChildByName( "FileDesc" ) );
  364. if ( m_pFileDescription )
  365. {
  366. m_pFileDescription->SetMultiline( true );
  367. m_pFileDescription->SetCatchEnterKey( true );
  368. m_pFileDescription->SetVerticalScrollbar( true );
  369. }
  370. m_pFilename = dynamic_cast< vgui::Label * >( FindChildByName( "SourceFile" ) );
  371. if ( !g_MapFilename.IsEmpty() )
  372. {
  373. m_pFilename->SetText( g_MapFilename );
  374. }
  375. m_pPublishButton = dynamic_cast< vgui::Button * >( FindChildByName( "ButtonPublish" ) );
  376. // If we're updating, change the context of the button
  377. if ( !m_bAddingNewFile )
  378. {
  379. m_pPublishButton->SetText( "Update" );
  380. m_pPublishButton->SetCommand( "Update" );
  381. }
  382. // Setup our initial state for the edit fields
  383. PopulateEditFields();
  384. SetPublishButtonState();
  385. }
  386. //-----------------------------------------------------------------------------
  387. // Purpose: Helper to build thumbnail name
  388. //-----------------------------------------------------------------------------
  389. void CFilePublishDialog::GetPreviewFilename( char *szOut, size_t outLen )
  390. {
  391. char szMapShortName[MAX_PATH];
  392. Q_FileBase( g_MapFilename, szMapShortName, sizeof(szMapShortName) );
  393. Q_snprintf( szOut, outLen, "%s_thumb.jpg", szMapShortName );
  394. }
  395. //-----------------------------------------------------------------------------
  396. // Purpose: Callback when our create item has completed. Need to do initial update.
  397. //-----------------------------------------------------------------------------
  398. void CFilePublishDialog::Steam_OnCreateItem( CreateItemResult_t *pResult, bool bError )
  399. {
  400. bError = bError || pResult->m_eResult != k_EResultOK;
  401. m_nFileID = pResult->m_nPublishedFileId;
  402. if ( bError )
  403. {
  404. HideStatusWindow();
  405. ErrorMessage( kFailedToPublishFile );
  406. if ( m_nFileID != k_PublishedFileIdInvalid )
  407. {
  408. // TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage.
  409. // Once this is fixed in steam, this call should probably be moved
  410. steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID );
  411. m_nFileID = k_PublishedFileIdInvalid;
  412. }
  413. }
  414. else
  415. {
  416. StartPrepareFile();
  417. }
  418. }
  419. //-----------------------------------------------------------------------------
  420. // Purpose: Callback from our map compression thread finishing
  421. //-----------------------------------------------------------------------------
  422. void CFilePublishDialog::OnFilePrepared( bool bSucceeded )
  423. {
  424. if ( bSucceeded )
  425. {
  426. // Move on to final publishing
  427. bSucceeded = UpdateFileInternal();
  428. }
  429. if ( bSucceeded )
  430. {
  431. // Done, waiting on file publish callback
  432. return;
  433. }
  434. // Failure
  435. // This is after OnCreateItem for new files, so cleanup the incomplete item on failure from either the compress or
  436. // kicking off the update.
  437. if ( m_bAddingNewFile && m_nFileID != k_PublishedFileIdInvalid )
  438. {
  439. // TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage.
  440. // Once this is fixed in steam, this call should probably be moved
  441. steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID );
  442. m_nFileID = k_PublishedFileIdInvalid;
  443. }
  444. HideStatusWindow();
  445. ErrorMessage( kFailedToUpdateFile );
  446. }
  447. //-----------------------------------------------------------------------------
  448. // Purpose: Callback when our publish call has completed
  449. //-----------------------------------------------------------------------------
  450. void CFilePublishDialog::Steam_OnPublishFile( SubmitItemUpdateResult_t *pResult, bool bError )
  451. {
  452. // Remove prepared map
  453. char szPreparedMap[MAX_PATH] = { 0 };
  454. V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ),
  455. szPreparedMap, sizeof( szPreparedMap ) );
  456. g_pFullFileSystem->RemoveFile( szPreparedMap, UGC_PATHID );
  457. // Remove local thumbnail
  458. CUtlBuffer bufData;
  459. char szPreviewFilename[MAX_PATH];
  460. GetPreviewFilename( szPreviewFilename, sizeof( szPreviewFilename ) );
  461. g_pFullFileSystem->RemoveFile( szPreviewFilename, UGC_PATHID );
  462. HideStatusWindow();
  463. if ( bError || pResult->m_eResult != k_EResultOK )
  464. {
  465. if ( m_bAddingNewFile && m_nFileID != k_PublishedFileIdInvalid )
  466. {
  467. // TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage.
  468. // Once this is fixed in steam, this call should probably be moved
  469. steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID );
  470. m_nFileID = k_PublishedFileIdInvalid;
  471. }
  472. ErrorMessage( kFailedToPublishFile );
  473. }
  474. else
  475. {
  476. EUniverse universe = GetUniverse();
  477. switch ( universe )
  478. {
  479. case k_EUniversePublic:
  480. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://steamcommunity.com/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) );
  481. break;
  482. case k_EUniverseBeta:
  483. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://beta.steamcommunity.com/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) );
  484. break;
  485. case k_EUniverseDev:
  486. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://localhost/community/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) );
  487. break;
  488. }
  489. // Tell our parent what happened
  490. KeyValues *pkvActionSignal = new KeyValues( "ChangedFile" );
  491. pkvActionSignal->SetUint64( "nPublishedFileID", m_nFileID );
  492. PostActionSignal( pkvActionSignal );
  493. // Close down the window
  494. CloseModal();
  495. }
  496. }
  497. //-----------------------------------------------------------------------------
  498. // Purpose: Share the file with Steam Cloud and return the handle for later usage
  499. //-----------------------------------------------------------------------------
  500. bool CFilePublishDialog::PublishFile()
  501. {
  502. // Must be a valid file
  503. ErrorCode_t errorCode = ValidateFile( g_MapFilename );
  504. #ifdef TF_CLIENT_DLL
  505. const char *pExt = V_GetFileExtension( g_MapFilename );
  506. if ( errorCode == kNoError && pExt && V_strcmp( pExt, "bsp" ) == 0 )
  507. {
  508. if ( !CTFMapsWorkshop::IsValidOriginalFileNameForMap( CUtlString( V_GetFileName( g_MapFilename ) ) ) )
  509. {
  510. errorCode = kInvalidMapName;
  511. }
  512. }
  513. #endif
  514. if ( errorCode != kNoError )
  515. {
  516. ErrorMessage( errorCode );
  517. return false;
  518. }
  519. ShowStatusWindow( kPublishing );
  520. EWorkshopFileType eFileType = WorkshipFileTypeForFile( g_MapFilename );
  521. // Create file on UGC
  522. SteamAPICall_t hSteamAPICall = steamapicontext->SteamUGC()->CreateItem( GetTargetAppID(), eFileType );
  523. // Set the callback
  524. m_callbackCreateItem.Set( hSteamAPICall, this, &CFilePublishDialog::Steam_OnCreateItem );
  525. return true;
  526. }
  527. //-----------------------------------------------------------------------------
  528. // Purpose: Kick off the map compression thread
  529. //-----------------------------------------------------------------------------
  530. void CFilePublishDialog::StartPrepareFile( void )
  531. {
  532. // Ensure temp dir exists
  533. g_pFullFileSystem->CreateDirHierarchy( WORKSHOP_TEMP_UPLOAD_DIR, UGC_PATHID );
  534. char szOutPath[MAX_PATH] = { 0 };
  535. V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ),
  536. szOutPath, sizeof( szOutPath ) );
  537. // Ensure this file isn't leftover in output dir
  538. g_pFullFileSystem->RemoveFile( szOutPath, UGC_PATHID );
  539. // Start thread
  540. Assert( !m_pPrepareFileThread );
  541. m_pPrepareFileThread = new CPrepareFileThread( g_MapFilename, szOutPath );
  542. m_pPrepareFileThread->Start();
  543. }
  544. //-----------------------------------------------------------------------------
  545. // Purpose: Parse commands coming in from the VGUI dialog
  546. //-----------------------------------------------------------------------------
  547. void CFilePublishDialog::SetPublishButtonState( void )
  548. {
  549. if ( m_bAddingNewFile )
  550. {
  551. if ( m_bValidFile && m_bValidJpeg )
  552. {
  553. m_pPublishButton->SetEnabled( true );
  554. }
  555. else
  556. {
  557. m_pPublishButton->SetEnabled( false );
  558. }
  559. }
  560. else // Updating a previous entry
  561. {
  562. // m_pPublishButton->SetEnabled( (m_nFileDetailsChanges!=0) );
  563. m_pPublishButton->SetEnabled( true ); // For now, always allow it. Worst case it's a no-op
  564. }
  565. }
  566. //-----------------------------------------------------------------------------
  567. // Purpose: Parse commands coming in from the VGUI dialog
  568. //-----------------------------------------------------------------------------
  569. bool CFilePublishDialog::UpdateFile( void )
  570. {
  571. // We should have been created for an existing file or published already, both of which set our ID.
  572. Assert( m_nFileID != k_PublishedFileIdInvalid );
  573. ShowStatusWindow( kUpdating );
  574. if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE )
  575. {
  576. StartPrepareFile();
  577. }
  578. else
  579. {
  580. // Not updating map, go straight to update step
  581. if ( !UpdateFileInternal() )
  582. {
  583. HideStatusWindow();
  584. ErrorMessage( kFailedToUpdateFile );
  585. }
  586. return false;
  587. }
  588. return true;
  589. }
  590. //-----------------------------------------------------------------------------
  591. // Purpose: Update a file, used by the create and update pathways to fill in a UGC item
  592. //-----------------------------------------------------------------------------
  593. bool CFilePublishDialog::UpdateFileInternal()
  594. {
  595. ISteamUGC *pUGC = steamapicontext->SteamUGC();
  596. UGCUpdateHandle_t hItem = pUGC->StartItemUpdate( GetTargetAppID(), m_nFileID );
  597. if ( hItem == k_UGCUpdateHandleInvalid )
  598. {
  599. UGCWarning( "StartItemUpdate failed\n" );
  600. return false;
  601. }
  602. bool bError = false;
  603. // create thumbnail
  604. CUtlBuffer bufData;
  605. char szPreviewFilename[MAX_PATH];
  606. GetPreviewFilename( szPreviewFilename, sizeof( szPreviewFilename ) );
  607. if ( !bError && ImgUtl_SaveBitmapToBuffer( bufData, GetPreviewBitmap(), kImageFileFormat_JPG ) == CE_SUCCESS )
  608. {
  609. bError = !g_pFullFileSystem->WriteFile( szPreviewFilename, UGC_PATHID, bufData );
  610. // Get full path to give steam
  611. g_pFullFileSystem->RelativePathToFullPath( szPreviewFilename, UGC_PATHID, szPreviewFilename, sizeof( szPreviewFilename ) );
  612. }
  613. else
  614. {
  615. bError = true;
  616. }
  617. // Get the compressed map out of the upload directory
  618. char szPreparedMap[MAX_PATH] = { 0 };
  619. char szFullPreparedPath[MAX_PATH] = { 0 };
  620. if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE )
  621. {
  622. V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ),
  623. szPreparedMap, sizeof( szPreparedMap ) );
  624. g_pFullFileSystem->RelativePathToFullPath( szPreparedMap, UGC_PATHID,
  625. szFullPreparedPath,
  626. sizeof( szFullPreparedPath ) );
  627. bError |= !*szFullPreparedPath;
  628. }
  629. if ( !bError )
  630. {
  631. // Set title
  632. char szTitle[k_cchPublishedDocumentTitleMax];
  633. m_pFileTitle->GetText( szTitle, sizeof(szTitle) );
  634. Q_AggressiveStripPrecedingAndTrailingWhitespace( szTitle );
  635. bError |= !pUGC->SetItemTitle( hItem, szTitle );
  636. // Set descriptor
  637. char szDesc[k_cchPublishedDocumentDescriptionMax];
  638. m_pFileDescription->GetText( szDesc, sizeof(szDesc) );
  639. Q_AggressiveStripPrecedingAndTrailingWhitespace( szDesc );
  640. bError |= !pUGC->SetItemDescription( hItem, szDesc );
  641. // Set thumbnail
  642. if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_PREVIEW )
  643. {
  644. bError |= !pUGC->SetItemPreview( hItem, szPreviewFilename );
  645. }
  646. // Set file
  647. if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE )
  648. {
  649. if ( *szFullPreparedPath )
  650. {
  651. bError |= !pUGC->SetItemContent( hItem, szFullPreparedPath );
  652. // Metadata for our files is just the original filename, since they are currently all single files
  653. bError |= !pUGC->SetItemMetadata( hItem, V_GetFileName( g_MapFilename.Get() ) );
  654. }
  655. else
  656. {
  657. UGCWarning( "Prepared map does not appear to exist\n" );
  658. bError = true;
  659. }
  660. }
  661. // Tags
  662. SteamParamStringArray_t strArray;
  663. PopulateTags( strArray );
  664. bError |= !pUGC->SetItemTags( hItem, &strArray );
  665. // Visibility
  666. bError |= !pUGC->SetItemVisibility( hItem, k_ERemoteStoragePublishedFileVisibilityPublic );
  667. }
  668. else
  669. {
  670. bError = true;
  671. }
  672. if ( !bError )
  673. {
  674. SteamAPICall_t hSteamAPICall = steamapicontext->SteamUGC()->SubmitItemUpdate( hItem, NULL );
  675. m_callbackPublishFile.Set( hSteamAPICall, this, &CFilePublishDialog::Steam_OnPublishFile );
  676. return true;
  677. }
  678. // Failed, cleanup prepared map
  679. g_pFullFileSystem->RemoveFile( szPreparedMap, UGC_PATHID );
  680. return false;
  681. }
  682. //-----------------------------------------------------------------------------
  683. // Purpose:
  684. //-----------------------------------------------------------------------------
  685. void CFilePublishDialog::PerformLayout()
  686. {
  687. BaseClass::PerformLayout();
  688. }
  689. //-----------------------------------------------------------------------------
  690. // Purpose: Parse commands coming in from the VGUI dialog
  691. //-----------------------------------------------------------------------------
  692. void CFilePublishDialog::OnCommand( const char *command )
  693. {
  694. if ( Q_stricmp( command, "Publish" ) == 0 )
  695. {
  696. // Verify they've filled everything out properly
  697. bool bHasTitle = ( m_pFileTitle->GetTextLength() > 0 );
  698. bool bHasDesc = ( m_pFileDescription->GetTextLength() > 0 );
  699. if ( !bHasTitle || !bHasDesc )
  700. {
  701. ErrorMessage( kNeedTitleAndDescription );
  702. return;
  703. }
  704. // Get our title
  705. char szTitle[k_cchPublishedDocumentTitleMax];
  706. m_pFileTitle->GetText( szTitle, sizeof(szTitle) );
  707. Q_AggressiveStripPrecedingAndTrailingWhitespace( szTitle );
  708. // Get our descriptor
  709. char szDesc[k_cchPublishedDocumentDescriptionMax];
  710. m_pFileDescription->GetText( szDesc, sizeof(szDesc) );
  711. Q_AggressiveStripPrecedingAndTrailingWhitespace( szDesc );
  712. bHasTitle = Q_strlen( szTitle ) != 0;
  713. bHasDesc = Q_strlen( szDesc ) != 0;
  714. if ( !bHasTitle || !bHasDesc )
  715. {
  716. ErrorMessage( kNeedTitleAndDescription );
  717. return;
  718. }
  719. PublishFile();
  720. }
  721. else if ( Q_stricmp( command, "Update" ) == 0 )
  722. {
  723. UpdateFile();
  724. }
  725. else if ( Q_stricmp( command, "MainFileMaps" ) == 0 )
  726. {
  727. m_fileOpenMode = FILEOPEN_MAIN_FILE;
  728. // Create a new dialog
  729. vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true );
  730. pDlg->AddFilter( GetFileTypes( IMPORT_FILTER_MAP ), GetFileTypeDescriptions( IMPORT_FILTER_MAP ), true );
  731. if ( !FStrEq( publish_file_last_dir.GetString(), "" ) )
  732. {
  733. pDlg->SetStartDirectory( publish_file_last_dir.GetString() );
  734. }
  735. char textBuffer[1024];
  736. m_pFilename->GetText( textBuffer, sizeof( textBuffer ) );
  737. char szFilePath[MAX_PATH];
  738. g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof(szFilePath) );
  739. strcat( szFilePath, "/" );
  740. strcat( szFilePath, textBuffer );
  741. // Get the currently set dir and use that as the start
  742. // pDlg->ExpandTreeToPath( szFilePath );
  743. pDlg->MoveToCenterOfScreen();
  744. pDlg->AddActionSignalTarget( this );
  745. pDlg->SetDeleteSelfOnClose( true );
  746. pDlg->DoModal();
  747. pDlg->Activate();
  748. }
  749. else if ( Q_stricmp( command, "MainFileOther" ) == 0 )
  750. {
  751. m_fileOpenMode = FILEOPEN_MAIN_FILE;
  752. // Create a new dialog
  753. vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true );
  754. pDlg->AddFilter( GetFileTypes( IMPORT_FILTER_OTHER ), GetFileTypeDescriptions( IMPORT_FILTER_OTHER ), true );
  755. if ( !FStrEq( publish_file_last_dir.GetString(), "" ) )
  756. {
  757. pDlg->SetStartDirectory( publish_file_last_dir.GetString() );
  758. }
  759. char textBuffer[1024];
  760. m_pFilename->GetText( textBuffer, sizeof( textBuffer ) );
  761. char szFilePath[MAX_PATH];
  762. g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof( szFilePath ) );
  763. strcat( szFilePath, "/" );
  764. strcat( szFilePath, textBuffer );
  765. // Get the currently set dir and use that as the start
  766. // pDlg->ExpandTreeToPath( szFilePath );
  767. pDlg->MoveToCenterOfScreen();
  768. pDlg->AddActionSignalTarget( this );
  769. pDlg->SetDeleteSelfOnClose( true );
  770. pDlg->DoModal();
  771. pDlg->Activate();
  772. }
  773. else if ( Q_stricmp( command, "PreviewBrowse" ) == 0 )
  774. {
  775. m_fileOpenMode = FILEOPEN_PREVIEW;
  776. // Create a new dialog
  777. vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true );
  778. pDlg->AddFilter( GetPreviewFileTypes(), GetPreviewFileTypeDescriptions(), true );
  779. if ( !FStrEq( publish_file_last_dir.GetString(), "" ) )
  780. {
  781. pDlg->SetStartDirectory( publish_file_last_dir.GetString() );
  782. }
  783. char szFilePath[MAX_PATH];
  784. g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof(szFilePath) );
  785. strcat( szFilePath, "/" );
  786. strcat( szFilePath, g_PreviewFilename );
  787. // Get the currently set dir and use that as the start
  788. // pDlg->ExpandTreeToPath( szFilePath );
  789. pDlg->MoveToCenterOfScreen();
  790. pDlg->AddActionSignalTarget( this );
  791. pDlg->SetDeleteSelfOnClose( true );
  792. pDlg->DoModal();
  793. pDlg->Activate();
  794. }
  795. else
  796. {
  797. BaseClass::OnCommand( command );
  798. }
  799. }
  800. //-----------------------------------------------------------------------------
  801. // Purpose: Take a filename, shorten it for display but retain the full path internally
  802. //-----------------------------------------------------------------------------
  803. CFilePublishDialog::ErrorCode_t CFilePublishDialog::ValidateFile( const char *lpszFilename )
  804. {
  805. return kNoError;
  806. }
  807. //-----------------------------------------------------------------------------
  808. // Purpose: Take a filename, shorten it for display but retain the full path internally
  809. //-----------------------------------------------------------------------------
  810. void CFilePublishDialog::SetFile( const char *lpszFilename, bool bImported )
  811. {
  812. // Must be a valid file
  813. ErrorCode_t errorCode = ValidateFile( lpszFilename );
  814. if ( errorCode != kNoError )
  815. {
  816. ErrorMessage( errorCode );
  817. return;
  818. }
  819. m_bValidFile = true;
  820. g_MapFilename = lpszFilename;
  821. char szShortName[ MAX_PATH ];
  822. Q_FileBase( g_MapFilename, szShortName, sizeof(szShortName) );
  823. const char *szExt = Q_GetFileExtension( lpszFilename );
  824. Q_SetExtension( szShortName, CFmtStr( ".%s", szExt ).Access(), sizeof(szShortName ) );
  825. m_pFilename->SetText( szShortName );
  826. // Notify of the change
  827. m_nFileDetailsChanges |= PFILE_FIELD_FILE;
  828. SetPublishButtonState();
  829. }
  830. //-----------------------------------------------------------------------------
  831. // Purpose: Notify us that the directory dialog has returned a new entry
  832. //-----------------------------------------------------------------------------
  833. void CFilePublishDialog::OnFileSelected( const char *fullPath )
  834. {
  835. char basepath[ MAX_PATH ];
  836. Q_ExtractFilePath( fullPath, basepath, sizeof( basepath ) );
  837. publish_file_last_dir.SetValue( basepath );
  838. if ( m_fileOpenMode == FILEOPEN_MAIN_FILE )
  839. {
  840. SetFile( fullPath );
  841. }
  842. else if ( m_fileOpenMode == FILEOPEN_PREVIEW )
  843. {
  844. // Notify of the change
  845. m_nFileDetailsChanges |= PFILE_FIELD_PREVIEW;
  846. SetPreviewImage( fullPath );
  847. }
  848. }