using Sandbox.Modals; using System.Text.Json.Nodes; namespace Sandbox; public static partial class Storage { /// /// A folder of content stored on disk /// public sealed class Entry { /// /// The identity of this content /// public string Id { get; private set; } /// /// The type of content, eg "save", "dupe" /// public string Type { get; private set; } /// /// Metadata about this content. This gets saved to disk automatically. /// private Dictionary Meta { get; } = new(); /// /// When this content was created /// public DateTimeOffset Created { get; private set; } = DateTimeOffset.UtcNow; /// /// This is where you save and load your files to /// public BaseFileSystem Files { get; private set; } private string DataPath => $"/storage/{Type}/{Id}/"; private string MetaPath => "_meta.json"; private string ThumbPath => "_thumb.png"; /// /// Creates a new storage entry with the specified type /// /// The content type (letters only, 1-16 characters) public Entry( string type ) { Type = type; Id = Guid.NewGuid().ToString(); ValidateType(); Sandbox.FileSystem.Data.CreateDirectory( DataPath ); Files = Sandbox.FileSystem.Data.CreateSubSystem( DataPath ); SaveMeta(); } internal Entry( StorageMeta meta ) { Id = meta.Id; Type = meta.Type; Meta = meta.Meta?.ToDictionary() ?? this.Meta; Created = meta.Timestamp; ValidateType(); Sandbox.FileSystem.Data.CreateDirectory( DataPath ); Files = Sandbox.FileSystem.Data.CreateSubSystem( DataPath ); } internal Entry( BaseFileSystem fs ) { var meta = fs.ReadJsonOrDefault( MetaPath ); if ( meta == null ) throw new System.Exception( "meta file not found" ); Id = meta.Id; Type = meta.Type; Meta = meta.Meta?.ToDictionary() ?? this.Meta; Created = meta.Timestamp; ValidateType(); Files = fs; } private void ValidateType() { ArgumentNullException.ThrowIfNull( Type ); if ( Type.Length < 1 ) throw new System.ArgumentException( "type cannot be empty", nameof( Type ) ); if ( Type.Length > 16 ) throw new System.ArgumentException( "type should be under 16 characters", nameof( Type ) ); // TODO - validate type and filename // type should be letters only, no symbols if ( !System.Text.RegularExpressions.Regex.IsMatch( Type, "^[a-zA-Z]+$" ) ) { throw new System.ArgumentException( "Invalid storage type", nameof( Type ) ); } } /// /// Set a meta value /// public void SetMeta( string key, T value ) { if ( Files.IsReadOnly ) return; if ( value == null ) { Meta.Remove( key ); return; } Meta[key] = JsonValue.Create( value )?.ToJsonString(); SaveMeta(); } /// /// Get a meta value /// public T GetMeta( string key, T defaultValue = default ) { if ( Meta.TryGetValue( key, out var val ) ) { return Json.Deserialize( val ); } return defaultValue; } void SaveMeta() { var meta = new StorageMeta { Id = this.Id, Type = this.Type, Meta = this.Meta, Timestamp = this.Created }; Files.WriteJson( MetaPath, meta ); } bool _thumbGenerated = false; Texture _thumbnail; /// /// Gets the thumbnail texture for this storage entry, if one exists /// public Texture Thumbnail { get { if ( _thumbGenerated ) return _thumbnail; _thumbGenerated = true; if ( !Files.FileExists( ThumbPath ) ) return null; var data = Files.ReadAllBytes( ThumbPath ); using var bitmap = Bitmap.CreateFromBytes( data.ToArray() ); _thumbnail = bitmap?.ToTexture(); return _thumbnail; } } /// /// Sets the thumbnail for this storage entry /// /// The bitmap to use as the thumbnail public void SetThumbnail( Bitmap bitmap ) { if ( Files.IsReadOnly ) return; _thumbGenerated = false; var png = bitmap.ToPng(); Files.WriteAllBytes( ThumbPath, png ); } /// /// Deletes this storage entry and all its files from disk /// public void Delete() { Sandbox.FileSystem.Data.DeleteDirectory( DataPath, true ); Storage.OnDeleted( this ); } /// /// Publishes this storage entry to the workshop /// /// The title for the workshop item /// Additional tags to apply to the workshop item /// Additional key-value pairs for the workshop item [Obsolete( "You can pass in a WorkshopPublishOptions now, which is way more flexible" )] public void Publish( string title = "Unnammed", string[] tags = null, Dictionary keyvalues = null ) { var o = new WorkshopPublishOptions { Title = title, StorageEntry = this, KeyValues = [], Tags = [Type] }; Publish( o ); } /// /// Publishes this storage entry to the workshop /// public void Publish( WorkshopPublishOptions options ) { var meta = Files.ReadAllText( MetaPath ); options.StorageEntry = this; options.KeyValues ??= []; options.Tags ??= []; options.Tags.Add( Type ); options.Metadata = meta; // assign the thumbnail if ( Files.FileExists( ThumbPath ) ) { var thumbData = Files.ReadAllBytes( ThumbPath ); var bitmap = Bitmap.CreateFromBytes( thumbData.ToArray() ); options.Thumbnail = bitmap; } options.KeyValues["type"] = Type; options.KeyValues["source"] = "storage"; Game.Overlay.WorkshopPublish( options ); } } }