From 68826583ab9719ce394a1909c883977ca2ca1cbc Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 27 Feb 2024 21:16:00 +0100 Subject: [PATCH] Fixed the runtime errors I've run in to so far --- src/Artemis.Core/Constants.cs | 12 +-- .../JsonConverters/ForgivingIntConverter.cs | 34 -------- src/Artemis.Core/Models/Profile/Folder.cs | 2 +- .../Profile/LayerProperties/LayerProperty.cs | 10 +-- .../LayerProperties/LayerPropertyKeyFrame.cs | 2 +- src/Artemis.Core/Plugins/PluginInfo.cs | 12 +++ .../Plugins/Settings/PluginSetting.cs | 8 +- .../Plugins/Settings/PluginSettings.cs | 2 +- src/Artemis.Core/Services/NodeService.cs | 4 +- .../Services/PluginManagementService.cs | 6 +- .../Services/Storage/ProfileService.cs | 8 +- .../EndPoints/DataModelJsonPluginEndPoint.cs | 84 +++++++++++++++++++ .../WebServer/Interfaces/IWebServerService.cs | 10 +++ .../Services/WebServer/WebServerService.cs | 18 +++- src/Artemis.Core/Utilities/CoreJson.cs | 21 ++--- .../VisualScripting/Nodes/NodeTStorage.cs | 4 +- .../AdaptionHints/IAdaptionHintEntity.cs | 8 +- .../Profile/Conditions/IConditionEntity.cs | 9 +- .../Profile/ProfileConfigurationEntity.cs | 3 +- .../Migrations/Profile/M0001NodeProviders.cs | 44 +++++----- .../M0002NodeProvidersProfileConfig.cs | 7 +- .../Migrations/Profile/M0003SystemTextJson.cs | 84 +++++++++++++++++++ .../StorageMigrationService.cs | 2 + .../DefaultDataModelDisplayViewModel.cs | 5 +- .../Extensions/ClipboardExtensions.cs | 2 +- .../Commands/ResetLayerProperty.cs | 4 +- .../Extensions/ProfileElementExtensions.cs | 16 ++-- src/Artemis.UI/Models/IClipboardModel.cs | 8 +- src/Artemis.UI/Models/LayerClipboardModel.cs | 18 +++- .../ProfileTree/FolderTreeItemViewModel.cs | 2 +- .../ProfileTree/LayerTreeItemViewModel.cs | 2 +- .../Keyframes/TimelineKeyframeViewModel.cs | 2 +- .../Sidebar/SidebarCategoryViewModel.cs | 6 +- .../VisualScripting/NodeScriptViewModel.cs | 9 +- .../Plugin/PluginSelectionStepViewModel.cs | 2 +- .../LayoutEntryUploadHandler.cs | 3 +- .../PluginEntryUploadHandler.cs | 4 +- .../ProfileEntryUploadHandler.cs | 4 +- 38 files changed, 333 insertions(+), 148 deletions(-) delete mode 100644 src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs create mode 100644 src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs create mode 100644 src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 56912df74..c61aa021c 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -92,14 +92,6 @@ public static class Constants /// public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); - internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")}; - internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")}; - - internal static JsonSerializerOptions JsonConvertSettings = new() - { - Converters = {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()} - }; - /// /// A read-only collection containing all primitive numeric types /// @@ -147,4 +139,8 @@ public static class Constants /// Gets the startup arguments provided to the application /// public static ReadOnlyCollection StartupArguments { get; set; } = null!; + + internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")}; + internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")}; + internal static readonly JsonSerializerOptions JsonConvertSettings = new() {Converters = {new SKColorConverter(), new NumericJsonConverter()}}; } \ No newline at end of file diff --git a/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs b/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs deleted file mode 100644 index 5663c73bc..000000000 --- a/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Artemis.Core.JsonConverters -{ - /// - /// An int converter that, if required, will round float values - /// - internal class ForgivingIntConverter : JsonConverter - { - public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - throw new JsonException("Cannot convert null value."); - - if (reader.TokenType == JsonTokenType.Number) - { - if (reader.TryGetInt32(out int intValue)) - return intValue; - - if (reader.TryGetDouble(out double doubleValue)) - return (int)Math.Round(doubleValue); - } - - throw new JsonException("Failed to deserialize forgiving int value"); - } - - public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index c86fdb46c..2db77c92b 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -164,7 +164,7 @@ public sealed class Folder : RenderProfileElement if (Parent == null) throw new ArtemisCoreException("Cannot create a copy of a folder without a parent"); - FolderEntity entityCopy = CoreJson.DeserializeObject(CoreJson.SerializeObject(FolderEntity))!; + FolderEntity entityCopy = CoreJson.Deserialize(CoreJson.Serialize(FolderEntity))!; entityCopy.Id = Guid.NewGuid(); entityCopy.Name += " - Copy"; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 6304bd388..5ecfeeca6 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -324,8 +324,8 @@ public class LayerProperty : CorePropertyChanged, ILayerProperty // Reference types make a deep clone (ab)using JSON else { - string json = CoreJson.SerializeObject(DefaultValue); - SetCurrentValue(CoreJson.DeserializeObject(json)!); + string json = CoreJson.Serialize(DefaultValue); + SetCurrentValue(CoreJson.Deserialize(json)!); } } @@ -420,7 +420,7 @@ public class LayerProperty : CorePropertyChanged, ILayerProperty try { - T? value = CoreJson.DeserializeObject(keyframeEntity.Value); + T? value = CoreJson.Deserialize(keyframeEntity.Value); if (value == null) return null; @@ -625,7 +625,7 @@ public class LayerProperty : CorePropertyChanged, ILayerProperty try { if (Entity.Value != null) - BaseValue = CoreJson.DeserializeObject(Entity.Value)!; + BaseValue = CoreJson.Deserialize(Entity.Value)!; } catch (JsonException) { @@ -664,7 +664,7 @@ public class LayerProperty : CorePropertyChanged, ILayerProperty if (!_isInitialized) throw new ArtemisCoreException("Layer property is not yet initialized"); - Entity.Value = CoreJson.SerializeObject(BaseValue); + Entity.Value = CoreJson.Serialize(BaseValue); Entity.KeyframesEnabled = KeyframesEnabled; Entity.KeyframeEntities.Clear(); Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity())); diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index b6f37c9ea..b5ceaa139 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -69,7 +69,7 @@ public class LayerPropertyKeyframe : CorePropertyChanged, ILayerPropertyKeyfr { return new KeyframeEntity { - Value = CoreJson.SerializeObject(Value), + Value = CoreJson.Serialize(Value), Position = Position, EasingFunction = (int) EasingFunction }; diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index da280694c..641471345 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -29,6 +29,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject private Uri? _license; private string? _licenseName; + [JsonConstructor] internal PluginInfo() { } @@ -37,6 +38,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// The plugins GUID /// [JsonRequired] + [JsonInclude] public Guid Guid { get => _guid; @@ -47,6 +49,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// The name of the plugin /// [JsonRequired] + [JsonInclude] public string Name { get => _name; @@ -130,6 +133,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// The version of the plugin /// [JsonRequired] + [JsonInclude] public string Version { get => _version; @@ -140,6 +144,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// The main entry DLL, should contain a class implementing Plugin /// [JsonRequired] + [JsonInclude] public string Main { get => _main; @@ -159,6 +164,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// /// Gets a boolean indicating whether this plugin requires elevated admin privileges /// + [JsonInclude] public bool RequiresAdmin { get => _requiresAdmin; @@ -177,6 +183,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// /// Gets /// + [JsonInclude] public PluginPlatform? Platforms { get => _platforms; @@ -186,6 +193,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// /// Gets the API version the plugin was built for /// + [JsonInclude] public Version? Api { get => _api; @@ -195,6 +203,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// /// Gets the plugin this info is associated with /// + [JsonIgnore] public Plugin Plugin { get => _plugin; @@ -204,6 +213,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject /// /// Gets a string representing either a full path pointing to an svg or the markdown icon /// + [JsonIgnore] public string? ResolvedIcon { get @@ -228,9 +238,11 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject } /// + [JsonIgnore] public List Prerequisites { get; } = new(); /// + [JsonIgnore] public IEnumerable PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform()); /// diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index 3b1e43cce..8f14d542f 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -23,7 +23,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting Name = pluginSettingEntity.Name; try { - _value = CoreJson.DeserializeObject(pluginSettingEntity.Value)!; + _value = CoreJson.Deserialize(pluginSettingEntity.Value)!; } catch (JsonException) { @@ -76,7 +76,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting public string Name { get; } /// - public bool HasChanged => CoreJson.SerializeObject(Value) != _pluginSettingEntity.Value; + public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value; /// public bool AutoSave { get; set; } @@ -84,7 +84,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting /// public void RejectChanges() { - Value = CoreJson.DeserializeObject(_pluginSettingEntity.Value); + Value = CoreJson.Deserialize(_pluginSettingEntity.Value); } /// @@ -93,7 +93,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting if (!HasChanged) return; - _pluginSettingEntity.Value = CoreJson.SerializeObject(Value); + _pluginSettingEntity.Value = CoreJson.Serialize(Value); _pluginRepository.SaveSetting(_pluginSettingEntity); OnSettingSaved(); } diff --git a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs index 84061e48e..89c1a10ea 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs @@ -49,7 +49,7 @@ public class PluginSettings { Name = name, PluginGuid = Plugin.Guid, - Value = CoreJson.SerializeObject(defaultValue) + Value = CoreJson.Serialize(defaultValue) }; _pluginRepository.AddSetting(settingEntity); } diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 11f6eb565..79f118749 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -41,12 +41,12 @@ internal class NodeService : INodeService public string ExportScript(NodeScript nodeScript) { nodeScript.Save(); - return CoreJson.SerializeObject(nodeScript.Entity); + return CoreJson.Serialize(nodeScript.Entity); } public void ImportScript(string json, NodeScript target) { - NodeScriptEntity? entity = CoreJson.DeserializeObject(json); + NodeScriptEntity? entity = CoreJson.Deserialize(json); if (entity == null) throw new ArtemisCoreException("Failed to load node script"); diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 75cd31cb6..67e3083e4 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -127,7 +127,7 @@ internal class PluginManagementService : IPluginManagementService throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName); using StreamReader reader = new(metaDataFileEntry.Open()); - PluginInfo builtInPluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!; + PluginInfo builtInPluginInfo = CoreJson.Deserialize(reader.ReadToEnd())!; string preferred = builtInPluginInfo.PreferredPluginDirectory; // Find the matching plugin in the plugin folder @@ -360,7 +360,7 @@ internal class PluginManagementService : IPluginManagementService _logger.Warning(new ArtemisPluginException("Couldn't find the plugins metadata file at " + metadataFile), "Plugin exception"); // PluginInfo contains the ID which we need to move on - PluginInfo pluginInfo = CoreJson.DeserializeObject(File.ReadAllText(metadataFile))!; + PluginInfo pluginInfo = CoreJson.Deserialize(File.ReadAllText(metadataFile))!; if (!pluginInfo.IsCompatible) return null; @@ -610,7 +610,7 @@ internal class PluginManagementService : IPluginManagementService throw new ArtemisPluginException("Couldn't find a plugin.json in " + fileName); using StreamReader reader = new(metaDataFileEntry.Open()); - PluginInfo pluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!; + PluginInfo pluginInfo = CoreJson.Deserialize(reader.ReadToEnd())!; if (!pluginInfo.Main.EndsWith(".dll")) throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file"); diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 7846fe3d3..5cafbd7db 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -410,8 +410,8 @@ internal class ProfileService : IProfileService if (profileEntity == null) throw new ArtemisCoreException("Could not locate profile entity"); - string configurationJson = CoreJson.SerializeObject(profileConfiguration.Entity); - string profileJson = CoreJson.SerializeObject(profileEntity); + string configurationJson = CoreJson.Serialize(profileConfiguration.Entity); + string profileJson = CoreJson.Serialize(profileEntity); MemoryStream archiveStream = new(); @@ -461,11 +461,11 @@ internal class ProfileService : IProfileService // Deserialize profile configuration to JObject await using Stream configurationStream = configurationEntry.Open(); using StreamReader configurationReader = new(configurationStream); - JsonObject? configurationJson = CoreJson.DeserializeObject(await configurationReader.ReadToEndAsync()); + JsonObject? configurationJson = CoreJson.Deserialize(await configurationReader.ReadToEndAsync()); // Deserialize profile to JObject await using Stream profileStream = profileEntry.Open(); using StreamReader profileReader = new(profileStream); - JsonObject? profileJson = CoreJson.DeserializeObject(await profileReader.ReadToEndAsync()); + JsonObject? profileJson = CoreJson.Deserialize(await profileReader.ReadToEndAsync()); // Before deserializing, apply any pending migrations MigrateProfile(configurationJson, profileJson); diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs new file mode 100644 index 000000000..438a638fc --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Artemis.Core.Modules; +using EmbedIO; + +namespace Artemis.Core.Services; + +/// +/// Represents a plugin web endpoint receiving an object of type and returning any +/// or . +/// Note: Both will be deserialized and serialized respectively using JSON. +/// +public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel, new() +{ + private readonly Module _module; + private readonly Action _update; + + internal DataModelJsonPluginEndPoint(Module module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule) + { + _module = module ?? throw new ArgumentNullException(nameof(module)); + _update = CreateUpdateAction(); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + /// + /// Whether or not the end point should throw an exception if deserializing the received JSON fails. + /// If set to malformed JSON is silently ignored; if set to malformed + /// JSON throws a . + /// + public bool ThrowOnFail { get; set; } + + /// + protected override async Task ProcessRequest(IHttpContext context) + { + if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put) + throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls"); + + context.Response.ContentType = MimeType.Json; + + using TextReader reader = context.OpenRequestText(); + try + { + T? dataModel = CoreJson.Deserialize(await reader.ReadToEndAsync()); + if (dataModel != null) + _update(dataModel, _module.DataModel); + } + catch (JsonException) + { + if (ThrowOnFail) + throw; + } + } + + private Action CreateUpdateAction() + { + ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source"); + ParameterExpression targetParameter = Expression.Parameter(typeof(T), "target"); + + IEnumerable assignments = typeof(T) + .GetProperties() + .Where(prop => prop.CanWrite && prop.GetSetMethod() != null && + prop.GetSetMethod()!.IsPublic && + !prop.IsDefined(typeof(JsonIgnoreAttribute), false) && + !prop.PropertyType.IsAssignableTo(typeof(IDataModelEvent))) + .Select(prop => + { + MemberExpression sourceProperty = Expression.Property(sourceParameter, prop); + MemberExpression targetProperty = Expression.Property(targetParameter, prop); + return Expression.Assign(targetProperty, sourceProperty); + }); + + BlockExpression body = Expression.Block(assignments); + + return Expression.Lambda>(body, sourceParameter, targetParameter).Compile(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 938b86c78..e65d626fe 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -44,6 +44,16 @@ public interface IWebServerService : IArtemisService /// The resulting end point JsonPluginEndPoint AddResponsiveJsonEndPoint(PluginFeature feature, string endPointName, Func requestHandler); + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The module whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + [Obsolete("This way of updating is too unpredictable in combination with nested events, use AddJsonEndPoint to update manually instead")] + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel, new(); + /// /// Adds a new endpoint for the given plugin feature receiving an a . /// diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 206444543..e0ab36288 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -4,8 +4,10 @@ using System.IO; using System.Linq; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; using Serilog; @@ -21,7 +23,7 @@ internal class WebServerService : IWebServerService, IDisposable private readonly PluginSetting _webServerEnabledSetting; private readonly PluginSetting _webServerPortSetting; private readonly object _webserverLock = new(); - private readonly JsonSerializerOptions _jsonOptions = new() {WriteIndented = true}; + private readonly JsonSerializerOptions _jsonOptions = new(CoreJson.GetJsonSerializerOptions()) {ReferenceHandler = ReferenceHandler.IgnoreCycles, WriteIndented = true}; private CancellationTokenSource? _cts; public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService) @@ -239,7 +241,17 @@ internal class WebServerService : IWebServerService, IDisposable PluginsModule.AddPluginEndPoint(endPoint); return endPoint; } - + + [Obsolete("Use AddJsonEndPoint(PluginFeature feature, string endPointName, Action requestHandler) instead")] + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel, new() + { + if (module == null) throw new ArgumentNullException(nameof(module)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(module, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + public void RemovePluginEndPoint(PluginEndPoint endPoint) { PluginsModule.RemovePluginEndPoint(endPoint); @@ -307,7 +319,7 @@ internal class WebServerService : IWebServerService, IDisposable context.Response.ContentType = MimeType.Json; await using TextWriter writer = context.OpenResponseText(); - string response = CoreJson.SerializeObject(new Dictionary + string response = CoreJson.Serialize(new Dictionary { {"StatusCode", context.Response.StatusCode}, {"StackTrace", exception.StackTrace}, diff --git a/src/Artemis.Core/Utilities/CoreJson.cs b/src/Artemis.Core/Utilities/CoreJson.cs index 5f1ff4a9d..fa9477073 100644 --- a/src/Artemis.Core/Utilities/CoreJson.cs +++ b/src/Artemis.Core/Utilities/CoreJson.cs @@ -9,22 +9,16 @@ namespace Artemis.Core; /// public static class CoreJson { - #region Serialize - /// /// Serializes the specified object to a JSON string. /// /// The object to serialize. /// A JSON string representation of the object. [DebuggerStepThrough] - public static string SerializeObject(object? value) + public static string Serialize(object? value) { return JsonSerializer.Serialize(value, Constants.JsonConvertSettings); } - - #endregion - - #region Deserialize /// /// Deserializes the JSON to the specified .NET type. @@ -34,10 +28,17 @@ public static class CoreJson /// The deserialized object from the JSON string. [DebuggerStepThrough] [return: MaybeNull] - public static T DeserializeObject(string value) + public static T Deserialize(string value) { return JsonSerializer.Deserialize(value, Constants.JsonConvertSettings); } - - #endregion + + /// + /// Gets a copy of the JSON serializer options used by Artemis Core + /// + /// A copy of the JSON serializer options used by Artemis Core + public static JsonSerializerOptions GetJsonSerializerOptions() + { + return new JsonSerializerOptions(Constants.JsonConvertSettings); + } } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs index fafb4e37f..3d091a4b1 100644 --- a/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs +++ b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs @@ -41,12 +41,12 @@ public abstract class Node : Node /// public override string SerializeStorage() { - return CoreJson.SerializeObject(Storage); + return CoreJson.Serialize(Storage); } /// public override void DeserializeStorage(string serialized) { - Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage); + Storage = CoreJson.Deserialize(serialized) ?? default(TStorage); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs b/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs index 57c914786..56444bd86 100644 --- a/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs @@ -1,3 +1,9 @@ -namespace Artemis.Storage.Entities.Profile.AdaptionHints; +using System.Text.Json.Serialization; +namespace Artemis.Storage.Entities.Profile.AdaptionHints; + +[JsonDerivedType(typeof(CategoryAdaptionHintEntity), "Category")] +[JsonDerivedType(typeof(DeviceAdaptionHintEntity), "Device")] +[JsonDerivedType(typeof(KeyboardSectionAdaptionHintEntity), "KeyboardSection")] +[JsonDerivedType(typeof(SingleLedAdaptionHintEntity), "SingleLed")] public interface IAdaptionHintEntity; \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs index f1f24ee14..93fc27834 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs @@ -1,3 +1,10 @@ -namespace Artemis.Storage.Entities.Profile.Abstract; +using System.Text.Json.Serialization; +using Artemis.Storage.Entities.Profile.Conditions; +namespace Artemis.Storage.Entities.Profile.Abstract; + +[JsonDerivedType(typeof(AlwaysOnConditionEntity), "AlwaysOn")] +[JsonDerivedType(typeof(EventConditionEntity), "Event")] +[JsonDerivedType(typeof(PlayOnceConditionEntity), "PlayOnce")] +[JsonDerivedType(typeof(StaticConditionEntity), "Static")] public interface IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs index a3eeef113..aa20adf7f 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs @@ -1,5 +1,6 @@ using System; using Artemis.Storage.Entities.Profile.Nodes; +using Serilog.Core; namespace Artemis.Storage.Entities.Profile; @@ -26,5 +27,5 @@ public class ProfileConfigurationEntity public Guid ProfileId { get; set; } public bool FadeInAndOut { get; set; } - public int Version { get; set; } + public int Version { get; set; } = StorageMigrationService.PROFILE_VERSION; } \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs b/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs index 347de14c3..701ab91ee 100644 --- a/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs +++ b/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs @@ -1,7 +1,4 @@ -using System; -using System.Text.Json; using System.Text.Json.Nodes; -using Serilog.Core; namespace Artemis.Storage.Migrations.Profile { @@ -17,34 +14,37 @@ namespace Artemis.Storage.Migrations.Profile /// public void Migrate(JsonObject configurationJson, JsonObject profileJson) { - JsonArray? folders = (JsonArray?) profileJson["Folders"]?["values"]; - JsonArray? layers = (JsonArray?) profileJson["Layers"]?["values"]; + JsonArray? folders = profileJson["Folders"]?["$values"]?.AsArray(); + JsonArray? layers = profileJson["Layers"]?["$values"]?.AsArray(); if (folders != null) { - foreach (JsonValue folder in folders) + foreach (JsonNode? folder in folders) MigrateProfileElement(folder); } if (layers != null) { - foreach (JsonValue layer in layers) + foreach (JsonNode? layer in layers) { MigrateProfileElement(layer); - MigratePropertyGroup(layer["GeneralPropertyGroup"]); - MigratePropertyGroup(layer["TransformPropertyGroup"]); - MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]); + MigratePropertyGroup(layer?["GeneralPropertyGroup"]); + MigratePropertyGroup(layer?["TransformPropertyGroup"]); + MigratePropertyGroup(layer?["LayerBrush"]?["PropertyGroup"]); } } } - private void MigrateProfileElement(JsonNode profileElement) + private void MigrateProfileElement(JsonNode? profileElement) { - JsonArray? layerEffects = (JsonArray?) profileElement["LayerEffects"]?["values"]; + if (profileElement == null) + return; + + JsonArray? layerEffects = profileElement["LayerEffects"]?["$values"]?.AsArray(); if (layerEffects != null) { - foreach (JsonValue layerEffect in layerEffects) - MigratePropertyGroup(layerEffect["PropertyGroup"]); + foreach (JsonNode? layerEffect in layerEffects) + MigratePropertyGroup(layerEffect?["PropertyGroup"]); } JsonNode? displayCondition = profileElement["DisplayCondition"]; @@ -57,28 +57,24 @@ namespace Artemis.Storage.Migrations.Profile if (propertyGroup == null) return; - JsonArray? properties = (JsonArray?) propertyGroup["Properties"]?["values"]; - JsonArray? propertyGroups = (JsonArray?) propertyGroup["PropertyGroups"]?["values"]; - + JsonArray? properties = propertyGroup["Properties"]?["$values"]?.AsArray(); + JsonArray? propertyGroups = propertyGroup["PropertyGroups"]?["$values"]?.AsArray(); if (properties != null) { - foreach (JsonValue property in properties) - MigrateNodeScript(property["DataBinding"]?["NodeScript"]); + foreach (JsonNode? property in properties) + MigrateNodeScript(property?["DataBinding"]?["NodeScript"]); } if (propertyGroups != null) { - foreach (JsonValue childPropertyGroup in propertyGroups) + foreach (JsonNode? childPropertyGroup in propertyGroups) MigratePropertyGroup(childPropertyGroup); } } private void MigrateNodeScript(JsonNode? nodeScript) { - if (nodeScript == null) - return; - - JsonArray? nodes = nodeScript["Nodes"]?.AsArray(); + JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray(); if (nodes == null) return; diff --git a/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs b/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs index e201dea21..8036092dd 100644 --- a/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs +++ b/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs @@ -16,13 +16,10 @@ internal class M0002NodeProvidersProfileConfig : IProfileMigration { MigrateNodeScript(configurationJson["ActivationCondition"]); } - + private void MigrateNodeScript(JsonNode? nodeScript) { - if (nodeScript == null) - return; - - JsonArray? nodes = nodeScript["Nodes"]?.AsArray(); + JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray(); if (nodes == null) return; diff --git a/src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs b/src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs new file mode 100644 index 000000000..cfd984f3d --- /dev/null +++ b/src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs @@ -0,0 +1,84 @@ +using System.Linq; +using System.Text.Json.Nodes; + +namespace Artemis.Storage.Migrations.Profile; + +/// +/// Migrates profiles to be deserializable by System.Text.Json, removing type information from the JSON arrays and most objects. +/// +internal class M0003SystemTextJson : IProfileMigration +{ + /// + public int Version => 3; + + /// + public void Migrate(JsonObject configurationJson, JsonObject profileJson) + { + ConvertToSystemTextJson(configurationJson); + ConvertToSystemTextJson(profileJson); + } + + private void ConvertToSystemTextJson(JsonObject jsonObject) + { + FilterType(jsonObject); + + // Recursively convert all JSON arrays from {$type: "xyz", $values: []} to [] + foreach ((string? key, JsonNode? value) in jsonObject.ToDictionary()) + { + if (value is not JsonObject obj) + continue; + + // if there is a $type and a $values, replace the entire node with $values regardless of the value of $type + if (obj["$type"] != null && obj["$values"] != null) + { + JsonArray? values = obj["$values"]?.AsArray(); + if (values != null) + { + obj.Remove("$values"); + jsonObject[key] = values; + foreach (JsonNode? jsonNode in values.ToList()) + { + if (jsonNode is JsonObject childObject) + ConvertToSystemTextJson(childObject); + } + } + + obj.Remove("$type"); + } + else + { + ConvertToSystemTextJson(obj); + } + } + } + + private void FilterType(JsonObject jsonObject) + { + // If there only a type, replace or remove it depending on whether there's a matching JsonDerivedType + // This could be done with reflection but that would mean this migration automatically gains new behaviour over time. + JsonNode? type = jsonObject["$type"]; + if (type != null) + { + // Adaption Hints + if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.CategoryAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "Category"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.DeviceAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "Device"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.KeyboardSectionAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "KeyboardSection"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.SingleLedAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "SingleLed"; + // Conditions + if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.AlwaysOnConditionEntity, Artemis.Storage") + jsonObject["$type"] = "AlwaysOn"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.EventConditionEntity, Artemis.Storage") + jsonObject["$type"] = "Event"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.PlayOnceConditionEntity, Artemis.Storage") + jsonObject["$type"] = "PlayOnce"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.StaticConditionEntity, Artemis.Storage") + jsonObject["$type"] = "Static"; + else + jsonObject.Remove("$type"); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/StorageMigrationService.cs b/src/Artemis.Storage/StorageMigrationService.cs index cf728023b..1d726ac8f 100644 --- a/src/Artemis.Storage/StorageMigrationService.cs +++ b/src/Artemis.Storage/StorageMigrationService.cs @@ -9,6 +9,8 @@ namespace Artemis.Storage; public class StorageMigrationService { + public const int PROFILE_VERSION = 3; + private readonly ILogger _logger; private readonly IList _migrations; private readonly LiteRepository _repository; diff --git a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs index 4ffea1412..1a7a44c09 100644 --- a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs +++ b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs @@ -17,7 +17,10 @@ internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel GetJsonAsync(this IClipboard clipboard, string format) { byte[]? bytes = (byte[]?) await clipboard.GetDataAsync(format); - return bytes == null ? default : CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes)); + return bytes == null ? default : CoreJson.Deserialize(Encoding.Unicode.GetString(bytes).TrimEnd('\0')); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ResetLayerProperty.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ResetLayerProperty.cs index 944d82cfe..bc79644cf 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ResetLayerProperty.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ResetLayerProperty.cs @@ -32,12 +32,12 @@ public class ResetLayerProperty : IProfileEditorCommand /// public void Execute() { - string json = CoreJson.SerializeObject(_layerProperty.DefaultValue); + string json = CoreJson.Serialize(_layerProperty.DefaultValue); if (_keyframesEnabled) _layerProperty.KeyframesEnabled = false; - _layerProperty.SetCurrentValue(CoreJson.DeserializeObject(json)!); + _layerProperty.SetCurrentValue(CoreJson.Deserialize(json)!); } /// diff --git a/src/Artemis.UI/Extensions/ProfileElementExtensions.cs b/src/Artemis.UI/Extensions/ProfileElementExtensions.cs index e91aa0807..111331393 100644 --- a/src/Artemis.UI/Extensions/ProfileElementExtensions.cs +++ b/src/Artemis.UI/Extensions/ProfileElementExtensions.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Artemis.Core; using Artemis.Storage.Entities.Profile; using Artemis.UI.Models; +using Artemis.UI.Shared.Extensions; using Avalonia; using Avalonia.Input; @@ -19,7 +20,7 @@ public static class ProfileElementExtensions public static async Task CopyToClipboard(this Folder folder) { DataObject dataObject = new(); - string copy = CoreJson.SerializeObject(new FolderClipboardModel(folder)); + string copy = CoreJson.Serialize(new FolderClipboardModel(folder)); dataObject.Set(ClipboardDataFormat, copy); await Shared.UI.Clipboard.SetDataObjectAsync(dataObject); } @@ -27,7 +28,7 @@ public static class ProfileElementExtensions public static async Task CopyToClipboard(this Layer layer) { DataObject dataObject = new(); - string copy = CoreJson.SerializeObject(layer.LayerEntity); + string copy = CoreJson.Serialize(layer.LayerEntity); dataObject.Set(ClipboardDataFormat, copy); await Shared.UI.Clipboard.SetDataObjectAsync(dataObject); } @@ -35,18 +36,13 @@ public static class ProfileElementExtensions public static async Task PasteChildFromClipboard(this Folder parent) { - byte[]? bytes = (byte[]?) await Shared.UI.Clipboard.GetDataAsync(ClipboardDataFormat); - if (bytes == null!) - return null; - - object? entity = CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes)); + IClipboardModel? entity = await Shared.UI.Clipboard.GetJsonAsync(ClipboardDataFormat); switch (entity) { case FolderClipboardModel folderClipboardModel: return folderClipboardModel.Paste(parent.Profile, parent); - case LayerEntity layerEntity: - layerEntity.Id = Guid.NewGuid(); - return new Layer(parent.Profile, parent, layerEntity, true); + case LayerClipboardModel layerClipboardModel: + return layerClipboardModel.Paste(parent); default: return null; } diff --git a/src/Artemis.UI/Models/IClipboardModel.cs b/src/Artemis.UI/Models/IClipboardModel.cs index 00273ee92..fc4667951 100644 --- a/src/Artemis.UI/Models/IClipboardModel.cs +++ b/src/Artemis.UI/Models/IClipboardModel.cs @@ -2,10 +2,10 @@ using System.Text.Json.Serialization; namespace Artemis.UI.Models; -[JsonDerivedType(typeof(LayerClipboardModel))] -[JsonDerivedType(typeof(FolderClipboardModel))] -[JsonDerivedType(typeof(KeyframeClipboardModel))] -[JsonDerivedType(typeof(NodesClipboardModel))] +[JsonDerivedType(typeof(LayerClipboardModel), "ClipboardLayer")] +[JsonDerivedType(typeof(FolderClipboardModel), "ClipboardFolder")] +[JsonDerivedType(typeof(KeyframeClipboardModel), "ClipboardKeyframe")] +[JsonDerivedType(typeof(NodesClipboardModel), "ClipboardNodes")] public interface IClipboardModel { } \ No newline at end of file diff --git a/src/Artemis.UI/Models/LayerClipboardModel.cs b/src/Artemis.UI/Models/LayerClipboardModel.cs index 7fcb184d2..5578df270 100644 --- a/src/Artemis.UI/Models/LayerClipboardModel.cs +++ b/src/Artemis.UI/Models/LayerClipboardModel.cs @@ -1,4 +1,7 @@ +using System; +using System.Text.Json.Serialization; using Artemis.Core; +using Artemis.Storage.Entities.Profile; namespace Artemis.UI.Models; @@ -6,8 +9,19 @@ public class LayerClipboardModel : IClipboardModel { public LayerClipboardModel(Layer layer) { - Layer = layer; + Layer = layer.LayerEntity; } - public Layer Layer { get; set; } + [JsonConstructor] + public LayerClipboardModel() + { + } + + public LayerEntity Layer { get; set; } = null!; + + public RenderProfileElement Paste(Folder parent) + { + Layer.Id = Guid.NewGuid(); + return new Layer(parent.Profile, parent, Layer, true); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs index fc47438fc..6ef385d02 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs @@ -36,7 +36,7 @@ public class FolderTreeItemViewModel : TreeItemViewModel { await ProfileEditorService.SaveProfileAsync(); - FolderEntity copy = CoreJson.DeserializeObject(CoreJson.SerializeObject(Folder.FolderEntity))!; + FolderEntity copy = CoreJson.Deserialize(CoreJson.Serialize(Folder.FolderEntity))!; copy.Id = Guid.NewGuid(); copy.Name = Folder.Parent.GetNewFolderName(copy.Name + " - copy"); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs index 77112701a..81cbcce7c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs @@ -37,7 +37,7 @@ public class LayerTreeItemViewModel : TreeItemViewModel { await ProfileEditorService.SaveProfileAsync(); - LayerEntity copy = CoreJson.DeserializeObject(CoreJson.SerializeObject(Layer.LayerEntity))!; + LayerEntity copy = CoreJson.Deserialize(CoreJson.Serialize(Layer.LayerEntity))!; copy.Id = Guid.NewGuid(); copy.Name = Layer.Parent.GetNewFolderName(copy.Name + " - copy"); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs index 6a92ba3ad..c568924bc 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs @@ -135,7 +135,7 @@ public partial class TimelineKeyframeViewModel : ActivatableViewModelBase, IT else keyframes.AddRange(_profileEditorService.SelectedKeyframes.Select(k => new KeyframeClipboardModel(k))); - string copy = CoreJson.SerializeObject(keyframes); + string copy = CoreJson.Serialize(keyframes); DataObject dataObject = new(); dataObject.Set(KeyframeClipboardModel.ClipboardDataFormat, copy); await Shared.UI.Clipboard.SetDataObjectAsync(dataObject); diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs index 67b642428..6120a814c 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs @@ -161,7 +161,7 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase // Removing this at some point in the future if (result[0].EndsWith("json")) { - ProfileConfigurationExportModel? exportModel = CoreJson.DeserializeObject(await File.ReadAllTextAsync(result[0])); + ProfileConfigurationExportModel? exportModel = CoreJson.Deserialize(await File.ReadAllTextAsync(result[0])); if (exportModel == null) { await _windowService.ShowConfirmContentDialog("Import profile", "Failed to import this profile, make sure it is a valid Artemis profile.", "Confirm", null); @@ -234,8 +234,8 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase { MemoryStream archiveStream = new(); - string configurationJson = CoreJson.SerializeObject(exportModel.ProfileConfigurationEntity); - string profileJson = CoreJson.SerializeObject(exportModel.ProfileEntity); + string configurationJson = CoreJson.Serialize(exportModel.ProfileConfigurationEntity); + string profileJson = CoreJson.Serialize(exportModel.ProfileEntity); // Create a ZIP archive using (ZipArchive archive = new(archiveStream, ZipArchiveMode.Create, true)) diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs index ad86a82fa..bcf74f587 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs @@ -15,6 +15,7 @@ using Artemis.UI.DryIoc.Factories; using Artemis.UI.Models; using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Shared; +using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor.Commands; using Avalonia; @@ -277,18 +278,14 @@ public partial class NodeScriptViewModel : ActivatableViewModelBase { List nodes = NodeViewModels.Where(vm => vm.IsSelected).Select(vm => vm.Node).Where(n => !n.IsDefaultNode && !n.IsExitNode).ToList(); DataObject dataObject = new(); - string copy = CoreJson.SerializeObject(new NodesClipboardModel(NodeScript, nodes)); + string copy = CoreJson.Serialize(new NodesClipboardModel(NodeScript, nodes)); dataObject.Set(CLIPBOARD_DATA_FORMAT, copy); await Shared.UI.Clipboard.SetDataObjectAsync(dataObject); } private async Task ExecutePasteSelected() { - byte[]? bytes = (byte[]?) await Shared.UI.Clipboard.GetDataAsync(CLIPBOARD_DATA_FORMAT); - if (bytes == null!) - return; - - NodesClipboardModel? nodesClipboardModel = CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes)); + NodesClipboardModel? nodesClipboardModel = await Shared.UI.Clipboard.GetJsonAsync(CLIPBOARD_DATA_FORMAT); if (nodesClipboardModel == null) return; diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs index a44a7e05b..2d8cc45d9 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs @@ -55,7 +55,7 @@ public partial class PluginSelectionStepViewModel : SubmissionViewModel throw new ArtemisPluginException("Couldn't find a plugin.json in " + files[0]); using StreamReader reader = new(metaDataFileEntry.Open()); - PluginInfo pluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!; + PluginInfo pluginInfo = CoreJson.Deserialize(reader.ReadToEnd())!; if (!pluginInfo.Main.EndsWith(".dll")) throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file"); diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs index d607fd536..38e2c9789 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs @@ -1,5 +1,6 @@ using System.IO.Compression; using System.Net.Http.Headers; +using System.Net.Http.Json; using Artemis.Core; using Artemis.WebClient.Workshop.Entities; using Artemis.WebClient.Workshop.Exceptions; @@ -68,7 +69,7 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler if (!response.IsSuccessStatusCode) return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}"); - Release? release = CoreJson.DeserializeObject(await response.Content.ReadAsStringAsync(cancellationToken)); + Release? release = await response.Content.ReadFromJsonAsync(cancellationToken); return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response"); } diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs index 0296c297f..f8c51034c 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs @@ -1,5 +1,5 @@ using System.Net.Http.Headers; -using Artemis.Core; +using System.Net.Http.Json; using Artemis.WebClient.Workshop.Entities; namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers; @@ -34,7 +34,7 @@ public class PluginEntryUploadHandler : IEntryUploadHandler if (!response.IsSuccessStatusCode) return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}"); - Release? release = CoreJson.DeserializeObject(await response.Content.ReadAsStringAsync(cancellationToken)); + Release? release = await response.Content.ReadFromJsonAsync(cancellationToken); return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response"); } } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs index 2280d4215..012491729 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs @@ -1,5 +1,5 @@ using System.Net.Http.Headers; -using Artemis.Core; +using System.Net.Http.Json; using Artemis.Core.Services; using Artemis.WebClient.Workshop.Entities; @@ -38,7 +38,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler if (!response.IsSuccessStatusCode) return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}"); - Release? release = CoreJson.DeserializeObject(await response.Content.ReadAsStringAsync(cancellationToken)); + Release? release = await response.Content.ReadFromJsonAsync(cancellationToken); return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response"); } } \ No newline at end of file