Files
sbox-public/game/addons/tools/Code/Editor/BatchPublisher/BatchPublisher.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

390 lines
8.7 KiB
C#

namespace Editor;
using Sandbox;
using Sandbox.Utility;
using System;
using Editor.Wizards;
public class BatchPublisher : BaseWindow
{
public static void FromAssets( Asset[] assets )
{
var p = new BatchPublisher( assets );
p.Show();
}
public static void FromAssetsWithEnablePublish( Asset[] assets )
{
if ( assets.All( x => x.Publishing.Enabled ) )
{
FromAssets( assets );
return;
}
var popup = new BatchMarkPublishedWidget( assets );
popup.SetModal( true, true );
popup.Hide();
popup.Show();
}
public List<Asset> Assets { get; set; }
public ScrollArea AssetList { get; set; }
public Button PublishButton { get; set; }
private BatchPublisher( IEnumerable<Asset> assets ) : base()
{
Size = new Vector2( 900, 670 );
MinimumSize = Size;
TranslucentBackground = true;
NoSystemBackground = true;
SetWindowIcon( "backup" );
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
WindowTitle = $"Batch Publish v1.0";
var row = Layout.AddRow();
BuildHeader( row );
AssetList = new ScrollArea( this );
AssetList.MinimumWidth = 700;
row.Add( AssetList, 100 );
AssetList.Canvas = new Widget( AssetList );
AssetList.Canvas.Layout = Layout.Column();
AssetList.Canvas.Layout.Margin = new Sandbox.UI.Margin( 8, 8, 16, 8 );
AssetList.Canvas.Layout.Spacing = 1;
var controls = Layout.AddRow();
controls.Margin = 16;
controls.Spacing = 8;
controls.AddStretchCell();
//controls.Add( new Button( "Refresh" ) { Clicked = () => _ = RefreshList( assets ) } );
PublishButton = controls.Add( new Button.Primary( "Publish" ) );
PublishButton.Enabled = false;
PublishButton.Clicked = () => _ = Publish();
_ = RefreshList( assets );
}
void BuildHeader( Layout layout )
{
var header = layout.AddColumn();
header.Margin = 16;
header.Spacing = 8;
header.Add( new Label.Subtitle( "Batch Publish" ) );
header.Add( new Label( "If you're editing multiple published assets it can be a pain to publish each one. Here you can publish all changed assets in one go." ) { WordWrap = true } );
header.Add( new Label( "You must own the rights to content you're publishing. Don't upload a bunch of content from someone else's game and write fair use like it's all fine - it will get removed." ) { WordWrap = true } );
header.Add( new Label( "You should visit <a href=\"https://sbox.game\">sbox.game</a> to fill in additional details, like description, tags and visibility. We won't change any of that here." ) { WordWrap = true } );
header.AddStretchCell();
header.Add( new Label( "Pay particular attention to the ident (in green) before first publish - you won't be able to edit this afterwards." ) { WordWrap = true } );
}
private async Task RefreshList( IEnumerable<Asset> assets )
{
EditorUtility.ClearPackageCache();
AssetList.Canvas.Layout.Clear( true );
Assets ??= new();
Assets.Clear();
foreach ( var asset in assets )
{
if ( !asset.Publishing.Enabled )
continue;
Assets.Add( asset );
var row = new AssetRow( this, asset );
AssetList.Canvas.Layout.Add( row, 0 );
}
AssetList.Canvas.Layout.AddStretchCell();
var t = new List<Task>();
foreach ( var a in AssetList.Canvas.Children.OfType<AssetRow>().OrderBy( x => Guid.NewGuid() ) )
{
if ( !a.IsValid ) continue;
t.Add( a.RefreshStatus() );
while ( t.Count > 32 )
{
await Task.WhenAny( t );
t.RemoveAll( x => x.IsCompleted );
}
}
await Task.WhenAll( t );
var changed = AssetList.Canvas.Children.OfType<AssetRow>().Where( x => x.IsValid ).Count( x => x.NeedsUpload );
if ( changed > 0 )
{
PublishButton.Text = $"Publish {changed} Assets";
PublishButton.Enabled = true;
}
else
{
PublishButton.Text = $"No Changes Detected";
PublishButton.Enabled = false;
}
}
async Task Publish()
{
Enabled = false;
HideCompleted();
var t = new List<Task>();
foreach ( var a in AssetList.Canvas.Children.OfType<AssetRow>() )
{
if ( !IsValid || !Visible ) return;
t.Add( a.Publish() );
while ( t.Count > 8 )
{
await Task.WhenAny( t );
t.RemoveAll( x => x.IsCompleted );
HideCompleted();
}
}
await Task.WhenAll( t );
//await RefreshList();
Enabled = true;
}
void HideCompleted()
{
foreach ( var a in AssetList.Canvas.Children.OfType<AssetRow>() )
{
if ( !a.NeedsUpload )
a.Visible = false;
}
}
}
file class AssetRow : Widget
{
Asset Asset { get; init; }
string _status;
string Status
{
get => _status;
set
{
if ( _status == value ) return;
_status = value;
Update();
}
}
public bool NeedsUpload { get; set; }
public AssetRow( Widget parent, Asset asset ) : base( parent )
{
Height = 25;
MinimumHeight = 25;
Asset = asset;
Layout = Layout.Row();
Layout.Spacing = 4;
RefreshRow();
}
void RefreshRow()
{
Layout.Clear( true );
Layout.AddStretchCell();
Layout.Add( new ToolButton( "", "drive_file_move", this ) { FixedHeight = 25, FixedWidth = 25, ToolTip = "Inspect Asset", MouseClick = InspectAsset } );
Layout.Add( new ToolButton( "", "edit", this ) { FixedHeight = 25, FixedWidth = 25, ToolTip = "Asset Properties", MouseClick = EditAsset } );
}
protected override void OnPaint()
{
base.OnPaint();
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
Paint.ClearPen();
Paint.SetBrush( Theme.ControlBackground );
Paint.DrawRect( LocalRect );
var fg = Color.White;
Paint.SetPen( Theme.Green );
var rect = LocalRect.Shrink( 8, 0 );
var r = Paint.DrawText( rect, $"{Asset.Publishing.ProjectConfig.FullIdent}", TextFlag.LeftCenter );
Paint.SetPen( fg.WithAlphaMultiplied( 0.5f ) );
rect.Left = r.Right + 8;
r = Paint.DrawText( rect, $"{Asset.Path}", TextFlag.LeftCenter );
Paint.SetPen( fg.WithAlphaMultiplied( 0.5f ) );
rect.Left = r.Right + 8;
rect.Right -= 70;
//r = Paint.DrawTextBox( rect, $"{Status}", Theme.Black, new Sandbox.UI.Margin( 4, 2 ), 4, TextFlag.RightCenter );
r = Paint.DrawText( rect, $"{Status}", TextFlag.RightCenter );
}
void InspectAsset()
{
EditorUtility.InspectorObject = Asset;
}
void EditAsset()
{
var addon = Asset.Publishing.CreateTemporaryProject();
addon.Config.Org = Project.Current.Config.Org;
ProjectInspector.OpenForProject( addon );
}
public async Task RefreshStatus()
{
if ( Asset.Publishing.ProjectConfig.Org != Project.Current.Config.Org && Asset.Publishing.ProjectConfig.Org == "local" )
{
Log.Info( $"Changing org to {Project.Current.Config.Org}" );
Asset.Publishing.ProjectConfig.Org = Project.Current.Config.Org;
Asset.Publishing.Save();
}
SetEffectOpacity( 1.0f );
NeedsUpload = true;
var package = await Package.FetchAsync( Asset.Publishing.ProjectConfig.FullIdent, false );
if ( package is null )
{
Status = "Doesn't Exist";
return;
}
if ( package.Revision is null )
{
Status = "No Revision";
return;
}
await package.Revision.DownloadManifestAsync();
var schema = package.Revision.Manifest;
if ( schema is null || schema.Files is null )
{
Status = "Invalid Revision";
return;
}
var files = Asset.GetReferences( true );
files.Add( Asset );
foreach ( var file in files )
{
if ( !ProjectPublisher.CanPublishFile( file ) ) continue;
var compileFileAbsPath = file.GetCompiledFile( true );
var compileFilePath = file.GetCompiledFile( false );
var existingFile = schema.Files.FirstOrDefault( x => x.Path == compileFilePath );
if ( existingFile.Path is null )
{
Status = $"Changed";
return;
}
var fileInfo = new System.IO.FileInfo( compileFileAbsPath );
// size the same?
if ( existingFile.Size != fileInfo.Length )
{
Status = $"Changed";
return;
}
using var reader = fileInfo.OpenRead();
var crc = await Crc64.FromStreamAsync( reader );
// checksum the same?
if ( existingFile.Crc != crc.ToString( "x" ) )
{
Status = $"Changed";
return;
}
}
Status = "";
SetEffectOpacity( 0.3f );
NeedsUpload = false;
}
public async Task Publish()
{
if ( !NeedsUpload )
return;
Status = "Scanning";
var upload = await ProjectPublisher.FromAsset( Asset );
try
{
Status = "Checking Files";
await upload.PrePublish();
if ( upload.MissingFileCount > 0 )
{
upload.OnProgressChanged = () => Status = $"{(upload.MissingFileSize).FormatBytes()}";
await upload.UploadFiles();
upload.OnProgressChanged = null;
}
Status = "Publishing";
await upload.Publish();
}
catch ( System.Exception e )
{
Log.Warning( e, $"Error when publising: {e.Message}" );
await RefreshStatus();
return;
}
Status = "Video";
await PublishWizard.UploadMediaPage.CreateAndUploadVideo( Asset, s => Status = s );
Status = "Completed";
EditorUtility.ClearPackageCache();
await RefreshStatus();
}
}