1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Merge branch 'development'

This commit is contained in:
RobertBeekman 2024-02-28 20:03:29 +01:00
commit e6565a2896
63 changed files with 466 additions and 584 deletions

View File

@ -42,7 +42,6 @@
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="McMaster.NETCore.Plugins" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="RGB.NET.Core" />
<PackageReference Include="RGB.NET.Layout" />
<PackageReference Include="RGB.NET.Presets" />
@ -50,6 +49,7 @@
<PackageReference Include="Serilog.Sinks.Debug" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>

View File

@ -4,10 +4,8 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using Artemis.Core.JsonConverters;
using Artemis.Core.Services;
using Artemis.Core.SkiaSharp;
using Newtonsoft.Json;
namespace Artemis.Core;
@ -62,7 +60,7 @@ public static class Constants
/// The full path to the Artemis user layouts folder
/// </summary>
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
/// <summary>
/// The full path to the Artemis user layouts folder
/// </summary>
@ -94,20 +92,6 @@ public static class Constants
/// </summary>
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 JsonSerializerSettings JsonConvertSettings = new()
{
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
};
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
{
TypeNameHandling = TypeNameHandling.All,
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
};
/// <summary>
/// A read-only collection containing all primitive numeric types
/// </summary>
@ -155,4 +139,8 @@ public static class Constants
/// Gets the startup arguments provided to the application
/// </summary>
public static ReadOnlyCollection<string> 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()}};
}

View File

@ -1,32 +0,0 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Artemis.Core.JsonConverters;
/// <summary>
/// An int converter that, if required, will round float values
/// </summary>
internal class ForgivingIntConverter : JsonConverter<int>
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JValue? jsonValue = serializer.Deserialize<JValue>(reader);
if (jsonValue == null)
throw new JsonReaderException("Failed to deserialize forgiving int value");
if (jsonValue.Type == JTokenType.Float)
return (int) Math.Round(jsonValue.Value<double>());
if (jsonValue.Type == JTokenType.Integer)
return jsonValue.Value<int>();
throw new JsonReaderException("Failed to deserialize forgiving int value");
}
}

View File

@ -1,26 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace Artemis.Core.JsonConverters
{
/// <summary>
/// Version converter that is forgiving of missing parts of the version string,
/// setting them to zero instead of -1.
/// </summary>
internal class ForgivingVersionConverter : VersionConverter
{
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
object? obj = base.ReadJson(reader, objectType, existingValue, serializer);
if (obj is not Version v)
return obj;
int major = v.Major == -1 ? 0 : v.Major;
int minor = v.Minor == -1 ? 0 : v.Minor;
int build = v.Build == -1 ? 0 : v.Build;
int revision = v.Revision == -1 ? 0 : v.Revision;
return new Version(major, minor, build, revision);
}
}
}

View File

@ -1,24 +1,26 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Artemis.Core.JsonConverters;
internal class NumericJsonConverter : JsonConverter<Numeric>
namespace Artemis.Core.JsonConverters
{
#region Overrides of JsonConverter<Numeric>
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
internal class NumericJsonConverter : JsonConverter<Numeric>
{
float floatValue = value;
writer.WriteValue(floatValue);
}
public override void Write(Utf8JsonWriter writer, Numeric value, JsonSerializerOptions options)
{
float floatValue = value;
writer.WriteNumberValue(floatValue);
}
/// <inheritdoc />
public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return new Numeric(reader.Value);
}
public override Numeric Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.Number)
{
throw new JsonException($"Expected a number token, but got {reader.TokenType}.");
}
#endregion
float floatValue = reader.GetSingle();
return new Numeric(floatValue);
}
}
}

View File

@ -1,21 +1,26 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using SkiaSharp;
namespace Artemis.Core.JsonConverters;
internal class SKColorConverter : JsonConverter<SKColor>
namespace Artemis.Core.JsonConverters
{
public override void WriteJson(JsonWriter writer, SKColor value, JsonSerializer serializer)
internal class SKColorConverter : JsonConverter<SKColor>
{
writer.WriteValue(value.ToString());
}
public override void Write(Utf8JsonWriter writer, SKColor value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
public override SKColor ReadJson(JsonReader reader, Type objectType, SKColor existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value is string value && !string.IsNullOrWhiteSpace(value))
return SKColor.Parse(value);
public override SKColor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException($"Expected a string token, but got {reader.TokenType}.");
}
return SKColor.Empty;
string colorString = reader.GetString() ?? string.Empty;
return SKColor.TryParse(colorString, out SKColor color) ? color : SKColor.Empty;
}
}
}

View File

@ -1,44 +0,0 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace Artemis.Core.JsonConverters;
/// <inheritdoc />
public class StreamConverter : JsonConverter<Stream>
{
#region Overrides of JsonConverter<Stream>
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, Stream? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
using MemoryStream memoryStream = new();
value.Position = 0;
value.CopyTo(memoryStream);
writer.WriteValue(memoryStream.ToArray());
}
/// <inheritdoc />
public override Stream? ReadJson(JsonReader reader, Type objectType, Stream? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value is not string base64)
return null;
if (existingValue == null || !hasExistingValue || !existingValue.CanRead)
return new MemoryStream(Convert.FromBase64String(base64));
using MemoryStream memoryStream = new(Convert.FromBase64String(base64));
existingValue.Position = 0;
memoryStream.CopyTo(existingValue);
existingValue.Position = 0;
return existingValue;
}
#endregion
}

View File

@ -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<FolderEntity>(CoreJson.SerializeObject(FolderEntity, true), true)!;
FolderEntity entityCopy = CoreJson.Deserialize<FolderEntity>(CoreJson.Serialize(FolderEntity))!;
entityCopy.Id = Guid.NewGuid();
entityCopy.Name += " - Copy";

View File

@ -2,8 +2,8 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.Json;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
namespace Artemis.Core;
@ -324,8 +324,8 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
// Reference types make a deep clone (ab)using JSON
else
{
string json = CoreJson.SerializeObject(DefaultValue, true);
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!);
string json = CoreJson.Serialize(DefaultValue);
SetCurrentValue(CoreJson.Deserialize<T>(json)!);
}
}
@ -420,7 +420,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
try
{
T? value = CoreJson.DeserializeObject<T>(keyframeEntity.Value);
T? value = CoreJson.Deserialize<T>(keyframeEntity.Value);
if (value == null)
return null;
@ -625,7 +625,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
try
{
if (Entity.Value != null)
BaseValue = CoreJson.DeserializeObject<T>(Entity.Value)!;
BaseValue = CoreJson.Deserialize<T>(Entity.Value)!;
}
catch (JsonException)
{
@ -664,7 +664,7 @@ public class LayerProperty<T> : 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()));

View File

@ -69,7 +69,7 @@ public class LayerPropertyKeyframe<T> : CorePropertyChanged, ILayerPropertyKeyfr
{
return new KeyframeEntity
{
Value = CoreJson.SerializeObject(Value),
Value = CoreJson.Serialize(Value),
Position = Position,
EasingFunction = (int) EasingFunction
};

View File

@ -1,36 +0,0 @@
using System;
using System.IO;
using Artemis.Core.JsonConverters;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
namespace Artemis.Core;
/// <summary>
/// A model that can be used to serialize a profile configuration, it's profile and it's icon
/// </summary>
public class ProfileConfigurationExportModel : IDisposable
{
/// <summary>
/// Gets or sets the storage entity of the profile configuration
/// </summary>
public ProfileConfigurationEntity? ProfileConfigurationEntity { get; set; }
/// <summary>
/// Gets or sets the storage entity of the profile
/// </summary>
[JsonProperty(Required = Required.Always)]
public ProfileEntity ProfileEntity { get; set; } = null!;
/// <summary>
/// Gets or sets a stream containing the profile image
/// </summary>
[JsonConverter(typeof(StreamConverter))]
public Stream? ProfileImage { get; set; }
/// <inheritdoc />
public void Dispose()
{
ProfileImage?.Dispose();
}
}

View File

@ -4,8 +4,8 @@ using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using Humanizer;
using Newtonsoft.Json;
namespace Artemis.Core.Modules;

View File

@ -3,14 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Plugins;
using Humanizer;
using Newtonsoft.Json;
namespace Artemis.Core;
/// <summary>
/// Represents basic info about a plugin feature and contains a reference to the instance of said feature
/// </summary>
[JsonObject(MemberSerialization.OptIn)]
public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
{
private string? _description;
@ -64,7 +62,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// The name of the feature
/// </summary>
[JsonProperty(Required = Required.Always)]
public string Name
{
get => _name;
@ -74,7 +71,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// A short description of the feature
/// </summary>
[JsonProperty]
public string? Description
{
get => _description;
@ -85,7 +81,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
/// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled.
/// <para>Note: always <see langword="true" /> if this is the plugin's only feature</para>
/// </summary>
[JsonProperty]
public bool AlwaysEnabled { get; internal set; }
/// <summary>

View File

@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Artemis.Core.JsonConverters;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Artemis.Core;
/// <summary>
/// Represents basic info about a plugin and contains a reference to the instance of said plugin
/// </summary>
[JsonObject(MemberSerialization.OptIn)]
public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
{
private Version? _api;
@ -28,10 +25,11 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
private string _version = null!;
private Uri? _website;
private Uri? _helpPage;
private bool _hotReloadSupported;
private bool _hotReloadSupported = true;
private Uri? _license;
private string? _licenseName;
[JsonConstructor]
internal PluginInfo()
{
}
@ -39,7 +37,8 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// The plugins GUID
/// </summary>
[JsonProperty(Required = Required.Always)]
[JsonRequired]
[JsonInclude]
public Guid Guid
{
get => _guid;
@ -49,7 +48,8 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// The name of the plugin
/// </summary>
[JsonProperty(Required = Required.Always)]
[JsonRequired]
[JsonInclude]
public string Name
{
get => _name;
@ -59,7 +59,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// A short description of the plugin
/// </summary>
[JsonProperty]
public string? Description
{
get => _description;
@ -69,7 +68,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets the author of this plugin
/// </summary>
[JsonProperty]
public string? Author
{
get => _author;
@ -79,7 +77,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets the website of this plugin or its author
/// </summary>
[JsonProperty]
public Uri? Website
{
get => _website;
@ -89,7 +86,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets the repository of this plugin
/// </summary>
[JsonProperty]
public Uri? Repository
{
get => _repository;
@ -99,7 +95,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets the help page of this plugin
/// </summary>
[JsonProperty]
public Uri? HelpPage
{
get => _helpPage;
@ -109,7 +104,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets the help page of this plugin
/// </summary>
[JsonProperty]
public Uri? License
{
get => _license;
@ -119,7 +113,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets the author of this plugin
/// </summary>
[JsonProperty]
public string? LicenseName
{
get => _licenseName;
@ -130,7 +123,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// The plugins display icon that's shown in the settings see <see href="https://materialdesignicons.com" /> for
/// available icons
/// </summary>
[JsonProperty]
public string? Icon
{
get => _icon;
@ -140,7 +132,8 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// The version of the plugin
/// </summary>
[JsonProperty(Required = Required.Always)]
[JsonRequired]
[JsonInclude]
public string Version
{
get => _version;
@ -150,7 +143,8 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// The main entry DLL, should contain a class implementing Plugin
/// </summary>
[JsonProperty(Required = Required.Always)]
[JsonRequired]
[JsonInclude]
public string Main
{
get => _main;
@ -161,8 +155,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// Gets or sets a boolean indicating whether this plugin should automatically enable all its features when it is first
/// loaded
/// </summary>
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool AutoEnableFeatures
{
get => _autoEnableFeatures;
@ -172,7 +164,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets a boolean indicating whether this plugin requires elevated admin privileges
/// </summary>
[JsonProperty]
[JsonInclude]
public bool RequiresAdmin
{
get => _requiresAdmin;
@ -182,8 +174,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets or sets a boolean indicating whether hot reloading this plugin is supported
/// </summary>
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool HotReloadSupported
{
get => _hotReloadSupported;
@ -193,7 +183,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets
/// </summary>
[JsonProperty]
[JsonInclude]
public PluginPlatform? Platforms
{
get => _platforms;
@ -203,8 +193,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets the API version the plugin was built for
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(ForgivingVersionConverter))]
[JsonInclude]
public Version? Api
{
get => _api;
@ -214,6 +203,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets the plugin this info is associated with
/// </summary>
[JsonIgnore]
public Plugin Plugin
{
get => _plugin;
@ -223,6 +213,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets a string representing either a full path pointing to an svg or the markdown icon
/// </summary>
[JsonIgnore]
public string? ResolvedIcon
{
get
@ -247,9 +238,11 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
}
/// <inheritdoc />
[JsonIgnore]
public List<PluginPrerequisite> Prerequisites { get; } = new();
/// <inheritdoc />
[JsonIgnore]
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
/// <inheritdoc />

View File

@ -1,6 +1,5 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Text.Json.Serialization;
namespace Artemis.Core;
@ -8,7 +7,7 @@ namespace Artemis.Core;
/// Specifies OS platforms a plugin may support.
/// </summary>
[Flags]
[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum PluginPlatform
{
/// <summary>The Windows platform.</summary>

View File

@ -1,7 +1,7 @@
using System;
using System.Text.Json;
using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
namespace Artemis.Core;
@ -23,9 +23,9 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
Name = pluginSettingEntity.Name;
try
{
_value = CoreJson.DeserializeObject<T>(pluginSettingEntity.Value)!;
_value = CoreJson.Deserialize<T>(pluginSettingEntity.Value)!;
}
catch (JsonReaderException)
catch (JsonException)
{
_value = default!;
}
@ -76,7 +76,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
public string Name { get; }
/// <inheritdoc />
public bool HasChanged => CoreJson.SerializeObject(Value) != _pluginSettingEntity.Value;
public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value;
/// <inheritdoc />
public bool AutoSave { get; set; }
@ -84,7 +84,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
/// <inheritdoc />
public void RejectChanges()
{
Value = CoreJson.DeserializeObject<T>(_pluginSettingEntity.Value);
Value = CoreJson.Deserialize<T>(_pluginSettingEntity.Value);
}
/// <inheritdoc />
@ -93,7 +93,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
if (!HasChanged)
return;
_pluginSettingEntity.Value = CoreJson.SerializeObject(Value);
_pluginSettingEntity.Value = CoreJson.Serialize(Value);
_pluginRepository.SaveSetting(_pluginSettingEntity);
OnSettingSaved();
}

View File

@ -49,7 +49,7 @@ public class PluginSettings
{
Name = name,
PluginGuid = Plugin.Guid,
Value = CoreJson.SerializeObject(defaultValue)
Value = CoreJson.Serialize(defaultValue)
};
_pluginRepository.AddSetting(settingEntity);
}

View File

@ -1,46 +0,0 @@
using System.Globalization;
using Newtonsoft.Json;
namespace Artemis.Core.Services.Core;
/// <summary>
/// Represents build information related to the currently running Artemis build
/// </summary>
public class BuildInfo
{
/// <summary>
/// Gets the unique ID of this build
/// </summary>
[JsonProperty]
public int BuildId { get; internal set; }
/// <summary>
/// Gets the build number. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
[JsonProperty]
public double BuildNumber { get; internal set; }
/// <summary>
/// Gets the build number formatted as a string. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
public string BuildNumberDisplay => BuildNumber.ToString(CultureInfo.InvariantCulture);
/// <summary>
/// Gets the branch of the triggering repo the build was created for.
/// </summary>
[JsonProperty]
public string SourceBranch { get; internal set; } = null!;
/// <summary>
/// Gets the commit ID used to create this build
/// </summary>
[JsonProperty]
public string SourceVersion { get; internal set; } = null!;
/// <summary>
/// Gets a boolean indicating whether the current build is a local build
/// </summary>
public bool IsLocalBuild { get; internal set; }
}

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using Artemis.Core.Modules;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services;

View File

@ -4,7 +4,6 @@ using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Artemis.Storage.Entities.Profile.Nodes;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core.Services;
@ -42,12 +41,12 @@ internal class NodeService : INodeService
public string ExportScript(NodeScript nodeScript)
{
nodeScript.Save();
return JsonConvert.SerializeObject(nodeScript.Entity, IProfileService.ExportSettings);
return CoreJson.Serialize(nodeScript.Entity);
}
public void ImportScript(string json, NodeScript target)
{
NodeScriptEntity? entity = JsonConvert.DeserializeObject<NodeScriptEntity>(json);
NodeScriptEntity? entity = CoreJson.Deserialize<NodeScriptEntity>(json);
if (entity == null)
throw new ArtemisCoreException("Failed to load node script");

View File

@ -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<PluginInfo>(reader.ReadToEnd())!;
PluginInfo builtInPluginInfo = CoreJson.Deserialize<PluginInfo>(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<PluginInfo>(File.ReadAllText(metadataFile))!;
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(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<PluginInfo>(reader.ReadToEnd())!;
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
if (!pluginInfo.Main.EndsWith(".dll"))
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");

View File

@ -2,7 +2,6 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core.Services;
@ -12,11 +11,6 @@ namespace Artemis.Core.Services;
/// </summary>
public interface IProfileService : IArtemisService
{
/// <summary>
/// Gets the JSON serializer settings used to import/export profiles.
/// </summary>
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
/// <summary>
/// Gets a read only collection containing all the profile categories.
/// </summary>

View File

@ -5,13 +5,13 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Migrations;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using SkiaSharp;
@ -410,8 +410,8 @@ internal class ProfileService : IProfileService
if (profileEntity == null)
throw new ArtemisCoreException("Could not locate profile entity");
string configurationJson = JsonConvert.SerializeObject(profileConfiguration.Entity, IProfileService.ExportSettings);
string profileJson = JsonConvert.SerializeObject(profileEntity, IProfileService.ExportSettings);
string configurationJson = CoreJson.Serialize(profileConfiguration.Entity);
string profileJson = CoreJson.Serialize(profileEntity);
MemoryStream archiveStream = new();
@ -461,21 +461,21 @@ internal class ProfileService : IProfileService
// Deserialize profile configuration to JObject
await using Stream configurationStream = configurationEntry.Open();
using StreamReader configurationReader = new(configurationStream);
JObject? configurationJson = JsonConvert.DeserializeObject<JObject>(await configurationReader.ReadToEndAsync(), IProfileService.ExportSettings);
JsonObject? configurationJson = CoreJson.Deserialize<JsonObject>(await configurationReader.ReadToEndAsync());
// Deserialize profile to JObject
await using Stream profileStream = profileEntry.Open();
using StreamReader profileReader = new(profileStream);
JObject? profileJson = JsonConvert.DeserializeObject<JObject>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
JsonObject? profileJson = CoreJson.Deserialize<JsonObject>(await profileReader.ReadToEndAsync());
// Before deserializing, apply any pending migrations
MigrateProfile(configurationJson, profileJson);
// Deserialize profile configuration to ProfileConfigurationEntity
ProfileConfigurationEntity? configurationEntity = configurationJson?.ToObject<ProfileConfigurationEntity>(JsonSerializer.Create(IProfileService.ExportSettings));
ProfileConfigurationEntity? configurationEntity = configurationJson?.Deserialize<ProfileConfigurationEntity>(Constants.JsonConvertSettings);
if (configurationEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize configuration.json");
// Deserialize profile to ProfileEntity
ProfileEntity? profileEntity = profileJson?.ToObject<ProfileEntity>(JsonSerializer.Create(IProfileService.ExportSettings));
ProfileEntity? profileEntity = profileJson?.Deserialize<ProfileEntity>(Constants.JsonConvertSettings);
if (profileEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json");
@ -559,7 +559,7 @@ internal class ProfileService : IProfileService
}
}
private void MigrateProfile(JObject? configurationJson, JObject? profileJson)
private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson)
{
if (configurationJson == null || profileJson == null)
return;
@ -568,7 +568,7 @@ internal class ProfileService : IProfileService
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
{
if (profileMigrator.Version <= configurationJson["Version"]!.Value<int>())
if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>())
continue;
profileMigrator.Migrate(configurationJson, profileJson);

View File

@ -1,9 +1,13 @@
using System;
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;
using Newtonsoft.Json;
namespace Artemis.Core.Services;
@ -15,10 +19,12 @@ namespace Artemis.Core.Services;
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel, new()
{
private readonly Module<T> _module;
private readonly Action<T, T> _update;
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
_module = module ?? throw new ArgumentNullException(nameof(module));
_update = CreateUpdateAction();
ThrowOnFail = true;
Accepts = MimeType.Json;
@ -31,8 +37,6 @@ public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
@ -44,7 +48,9 @@ public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
using TextReader reader = context.OpenRequestText();
try
{
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
T? dataModel = CoreJson.Deserialize<T>(await reader.ReadToEndAsync());
if (dataModel != null)
_update(dataModel, _module.DataModel);
}
catch (JsonException)
{
@ -53,5 +59,26 @@ public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
}
}
#endregion
private Action<T, T> CreateUpdateAction()
{
ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
ParameterExpression targetParameter = Expression.Parameter(typeof(T), "target");
IEnumerable<BinaryExpression> 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<Action<T, T>>(body, sourceParameter, targetParameter).Compile();
}
}

View File

@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services;
@ -52,7 +52,7 @@ public class JsonPluginEndPoint<T> : PluginEndPoint
object? response = null;
try
{
T? deserialized = JsonConvert.DeserializeObject<T>(await reader.ReadToEndAsync());
T? deserialized = JsonSerializer.Deserialize<T>(await reader.ReadToEndAsync());
if (deserialized == null)
throw new JsonException("Deserialization returned null");
@ -74,7 +74,7 @@ public class JsonPluginEndPoint<T> : PluginEndPoint
}
await using TextWriter writer = context.OpenResponseText();
await writer.WriteAsync(JsonConvert.SerializeObject(response));
await writer.WriteAsync(JsonSerializer.Serialize(response));
}
#endregion

View File

@ -1,7 +1,7 @@
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services;

View File

@ -43,7 +43,7 @@ public interface IWebServerService : IArtemisService
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
/// </summary>
@ -51,8 +51,9 @@ public interface IWebServerService : IArtemisService
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
[Obsolete("This way of updating is too unpredictable in combination with nested events, use AddJsonEndPoint<T> to update manually instead")]
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new();
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary>

View File

@ -3,12 +3,13 @@ using System.Collections.Generic;
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 Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services;
@ -22,6 +23,7 @@ internal class WebServerService : IWebServerService, IDisposable
private readonly PluginSetting<bool> _webServerEnabledSetting;
private readonly PluginSetting<int> _webServerPortSetting;
private readonly object _webserverLock = new();
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)
@ -120,7 +122,7 @@ internal class WebServerService : IWebServerService, IDisposable
Server = null;
}
WebApiModule apiModule = new("/", JsonNetSerializer);
WebApiModule apiModule = new("/", SystemTextJsonSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager()
@ -138,7 +140,7 @@ internal class WebServerService : IWebServerService, IDisposable
// Add registered controllers to the API module
foreach (WebApiControllerRegistration registration in _controllers)
apiModule.RegisterController(registration.ControllerType, (Func<WebApiController>) registration.UntypedFactory);
// Listen for state changes.
server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState);
@ -173,7 +175,7 @@ internal class WebServerService : IWebServerService, IDisposable
OnWebServerStarted();
}
}
private void AutoStartWebServer()
{
try
@ -240,6 +242,7 @@ internal class WebServerService : IWebServerService, IDisposable
return endPoint;
}
[Obsolete("Use AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler) instead")]
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new()
{
if (module == null) throw new ArgumentNullException(nameof(module));
@ -316,7 +319,7 @@ internal class WebServerService : IWebServerService, IDisposable
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string response = JsonConvert.SerializeObject(new Dictionary<string, object?>
string response = CoreJson.Serialize(new Dictionary<string, object?>
{
{"StatusCode", context.Response.StatusCode},
{"StackTrace", exception.StackTrace},
@ -331,17 +334,16 @@ internal class WebServerService : IWebServerService, IDisposable
await writer.WriteAsync(response);
}
private async Task JsonNetSerializer(IHttpContext context, object? data)
private async Task SystemTextJsonSerializer(IHttpContext context, object? data)
{
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string json = JsonConvert.SerializeObject(data, new JsonSerializerSettings {PreserveReferencesHandling = PreserveReferencesHandling.Objects});
await writer.WriteAsync(json);
await writer.WriteAsync(JsonSerializer.Serialize(data, _jsonOptions));
}
private async Task HandleHttpExceptionJson(IHttpContext context, IHttpException httpException)
{
await context.SendStringAsync(JsonConvert.SerializeObject(httpException, Formatting.Indented), MimeType.Json, Encoding.UTF8);
await context.SendStringAsync(JsonSerializer.Serialize(httpException, _jsonOptions), MimeType.Json, Encoding.UTF8);
}
#endregion

View File

@ -1,7 +1,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using System.Text.Json;
namespace Artemis.Core;
@ -10,62 +9,36 @@ namespace Artemis.Core;
/// </summary>
public static class CoreJson
{
#region Serialize
/// <summary>
/// Serializes the specified object to a JSON string.
/// </summary>
/// <param name="value">The object to serialize.</param>
/// <param name="handleTypeNames">If set to true sets TypeNameHandling to <see cref="TypeNameHandling.All" /></param>
/// <returns>A JSON string representation of the object.</returns>
[DebuggerStepThrough]
public static string SerializeObject(object? value, bool handleTypeNames = false)
public static string Serialize(object? value)
{
return JsonConvert.SerializeObject(value, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
return JsonSerializer.Serialize(value, Constants.JsonConvertSettings);
}
#endregion
#region Deserialize
/// <summary>
/// Deserializes the JSON to a .NET object.
/// </summary>
/// <param name="value">The JSON to deserialize.</param>
/// <param name="handleTypeNames">If set to true sets TypeNameHandling to <see cref="TypeNameHandling.All" /></param>
/// <returns>The deserialized object from the JSON string.</returns>
[DebuggerStepThrough]
public static object? DeserializeObject(string value, bool handleTypeNames = false)
{
return JsonConvert.DeserializeObject(value, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
}
/// <summary>
/// Deserializes the JSON to the specified .NET type.
/// </summary>
/// <param name="value">The JSON to deserialize.</param>
/// <param name="type">The <see cref="Type" /> of object being deserialized.</param>
/// <param name="handleTypeNames">If set to true sets TypeNameHandling to <see cref="TypeNameHandling.All" /></param>
/// <returns>The deserialized object from the JSON string.</returns>
[DebuggerStepThrough]
public static object? DeserializeObject(string value, Type type, bool handleTypeNames = false)
{
return JsonConvert.DeserializeObject(value, type, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
}
/// <summary>
/// Deserializes the JSON to the specified .NET type.
/// </summary>
/// <typeparam name="T">The type of the object to deserialize to.</typeparam>
/// <param name="value">The JSON to deserialize.</param>
/// <param name="handleTypeNames">If set to true sets TypeNameHandling to <see cref="TypeNameHandling.All" /></param>
/// <returns>The deserialized object from the JSON string.</returns>
[DebuggerStepThrough]
[return: MaybeNull]
public static T DeserializeObject<T>(string value, bool handleTypeNames = false)
public static T Deserialize<T>(string value)
{
return JsonConvert.DeserializeObject<T>(value, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
return JsonSerializer.Deserialize<T>(value, Constants.JsonConvertSettings);
}
/// <summary>
/// Gets a copy of the JSON serializer options used by Artemis Core
/// </summary>
/// <returns>A copy of the JSON serializer options used by Artemis Core</returns>
public static JsonSerializerOptions GetJsonSerializerOptions()
{
return new JsonSerializerOptions(Constants.JsonConvertSettings);
}
#endregion
}

View File

@ -41,12 +41,12 @@ public abstract class Node<TStorage> : Node
/// <inheritdoc />
public override string SerializeStorage()
{
return CoreJson.SerializeObject(Storage, true);
return CoreJson.Serialize(Storage);
}
/// <inheritdoc />
public override void DeserializeStorage(string serialized)
{
Storage = CoreJson.DeserializeObject<TStorage>(serialized) ?? default(TStorage);
Storage = CoreJson.Deserialize<TStorage>(serialized) ?? default(TStorage);
}
}

View File

@ -1,5 +1,4 @@
using System;
using Newtonsoft.Json;
namespace Artemis.Core;
@ -10,7 +9,6 @@ public sealed class InputPin<T> : Pin
{
#region Constructors
[JsonConstructor]
internal InputPin(INode node, string name)
: base(node, name)
{

View File

@ -1,5 +1,4 @@
using System;
using Newtonsoft.Json;
namespace Artemis.Core;
@ -10,7 +9,6 @@ public sealed class OutputPin<T> : Pin
{
#region Constructors
[JsonConstructor]
internal OutputPin(INode node, string name)
: base(node, name)
{

View File

@ -9,6 +9,6 @@
<ItemGroup>
<PackageReference Include="LiteDB" />
<PackageReference Include="Serilog" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -1,9 +1,9 @@
using Newtonsoft.Json.Linq;
using System.Text.Json.Nodes;
namespace Artemis.Storage.Migrations;
public interface IProfileMigration
{
int Version { get; }
void Migrate(JObject configurationJson, JObject profileJson);
void Migrate(JsonObject configurationJson, JsonObject profileJson);
}

View File

@ -1,88 +1,91 @@
using Newtonsoft.Json.Linq;
using System.Text.Json.Nodes;
namespace Artemis.Storage.Migrations.Profile;
/// <summary>
/// Migrates nodes to be provider-based.
/// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin.
/// </summary>
internal class M0001NodeProviders : IProfileMigration
namespace Artemis.Storage.Migrations.Profile
{
/// <inheritdoc />
public int Version => 1;
/// <inheritdoc />
public void Migrate(JObject configurationJson, JObject profileJson)
/// <summary>
/// Migrates nodes to be provider-based.
/// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin.
/// </summary>
internal class M0001NodeProviders : IProfileMigration
{
JArray? folders = (JArray?) profileJson["Folders"]?["$values"];
JArray? layers = (JArray?) profileJson["Layers"]?["$values"];
/// <inheritdoc />
public int Version => 1;
if (folders != null)
/// <inheritdoc />
public void Migrate(JsonObject configurationJson, JsonObject profileJson)
{
foreach (JToken folder in folders)
MigrateProfileElement(folder);
JsonArray? folders = profileJson["Folders"]?["$values"]?.AsArray();
JsonArray? layers = profileJson["Layers"]?["$values"]?.AsArray();
if (folders != null)
{
foreach (JsonNode? folder in folders)
MigrateProfileElement(folder);
}
if (layers != null)
{
foreach (JsonNode? layer in layers)
{
MigrateProfileElement(layer);
MigratePropertyGroup(layer?["GeneralPropertyGroup"]);
MigratePropertyGroup(layer?["TransformPropertyGroup"]);
MigratePropertyGroup(layer?["LayerBrush"]?["PropertyGroup"]);
}
}
}
if (layers != null)
private void MigrateProfileElement(JsonNode? profileElement)
{
foreach (JToken layer in layers)
if (profileElement == null)
return;
JsonArray? layerEffects = profileElement["LayerEffects"]?["$values"]?.AsArray();
if (layerEffects != null)
{
MigrateProfileElement(layer);
MigratePropertyGroup(layer["GeneralPropertyGroup"]);
MigratePropertyGroup(layer["TransformPropertyGroup"]);
MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]);
foreach (JsonNode? layerEffect in layerEffects)
MigratePropertyGroup(layerEffect?["PropertyGroup"]);
}
JsonNode? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition["Script"]);
}
private void MigratePropertyGroup(JsonNode? propertyGroup)
{
if (propertyGroup == null)
return;
JsonArray? properties = propertyGroup["Properties"]?["$values"]?.AsArray();
JsonArray? propertyGroups = propertyGroup["PropertyGroups"]?["$values"]?.AsArray();
if (properties != null)
{
foreach (JsonNode? property in properties)
MigrateNodeScript(property?["DataBinding"]?["NodeScript"]);
}
if (propertyGroups != null)
{
foreach (JsonNode? childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup);
}
}
private void MigrateNodeScript(JsonNode? nodeScript)
{
JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray();
if (nodes == null)
return;
foreach (JsonNode? jsonNode in nodes)
{
if (jsonNode == null)
continue;
JsonObject nodeObject = jsonNode.AsObject();
nodeObject["Type"] = nodeObject["Type"]?.GetValue<string>().Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
nodeObject["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}
private void MigrateProfileElement(JToken profileElement)
{
JArray? layerEffects = (JArray?) profileElement["LayerEffects"]?["$values"];
if (layerEffects != null)
{
foreach (JToken layerEffect in layerEffects)
MigratePropertyGroup(layerEffect["PropertyGroup"]);
}
JToken? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition["Script"]);
}
private void MigratePropertyGroup(JToken? propertyGroup)
{
if (propertyGroup == null || !propertyGroup.HasValues)
return;
JArray? properties = (JArray?) propertyGroup["Properties"]?["$values"];
JArray? propertyGroups = (JArray?) propertyGroup["PropertyGroups"]?["$values"];
if (properties != null)
{
foreach (JToken property in properties)
MigrateNodeScript(property["DataBinding"]?["NodeScript"]);
}
if (propertyGroups != null)
{
foreach (JToken childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup);
}
}
private void MigrateNodeScript(JToken? nodeScript)
{
if (nodeScript == null || !nodeScript.HasValues)
return;
JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"];
if (nodes == null)
return;
foreach (JToken node in nodes)
{
node["Type"] = node["Type"]?.Value<string>()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}
}

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json.Linq;
using System.Text.Json.Nodes;
namespace Artemis.Storage.Migrations.Profile;
@ -12,24 +12,24 @@ internal class M0002NodeProvidersProfileConfig : IProfileMigration
public int Version => 2;
/// <inheritdoc />
public void Migrate(JObject configurationJson, JObject profileJson)
public void Migrate(JsonObject configurationJson, JsonObject profileJson)
{
MigrateNodeScript(configurationJson["ActivationCondition"]);
}
private void MigrateNodeScript(JToken? nodeScript)
{
if (nodeScript == null || !nodeScript.HasValues)
return;
JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"];
private void MigrateNodeScript(JsonNode? nodeScript)
{
JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray();
if (nodes == null)
return;
foreach (JToken node in nodes)
foreach (JsonNode? jsonNode in nodes)
{
node["Type"] = node["Type"]?.Value<string>()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
if (jsonNode == null)
continue;
JsonObject nodeObject = jsonNode.AsObject();
nodeObject["Type"] = nodeObject["Type"]?.GetValue<string>().Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
nodeObject["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -0,0 +1,84 @@
using System.Linq;
using System.Text.Json.Nodes;
namespace Artemis.Storage.Migrations.Profile;
/// <summary>
/// Migrates profiles to be deserializable by System.Text.Json, removing type information from the JSON arrays and most objects.
/// </summary>
internal class M0003SystemTextJson : IProfileMigration
{
/// <inheritdoc />
public int Version => 3;
/// <inheritdoc />
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)
{
// Replace or remove $type 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<string>() == "Artemis.Storage.Entities.Profile.AdaptionHints.CategoryAdaptionHintEntity, Artemis.Storage")
jsonObject["$type"] = "Category";
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.AdaptionHints.DeviceAdaptionHintEntity, Artemis.Storage")
jsonObject["$type"] = "Device";
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.AdaptionHints.KeyboardSectionAdaptionHintEntity, Artemis.Storage")
jsonObject["$type"] = "KeyboardSection";
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.AdaptionHints.SingleLedAdaptionHintEntity, Artemis.Storage")
jsonObject["$type"] = "SingleLed";
// Conditions
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.Conditions.AlwaysOnConditionEntity, Artemis.Storage")
jsonObject["$type"] = "AlwaysOn";
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.Conditions.EventConditionEntity, Artemis.Storage")
jsonObject["$type"] = "Event";
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.Conditions.PlayOnceConditionEntity, Artemis.Storage")
jsonObject["$type"] = "PlayOnce";
else if (type.GetValue<string>() == "Artemis.Storage.Entities.Profile.Conditions.StaticConditionEntity, Artemis.Storage")
jsonObject["$type"] = "Static";
else
jsonObject.Remove("$type");
}
}
}

View File

@ -31,12 +31,24 @@ internal class ProfileCategoryRepository : IProfileCategoryRepository
public List<ProfileCategoryEntity> GetAll()
{
return _repository.Query<ProfileCategoryEntity>().ToList();
List<ProfileCategoryEntity> categories = _repository.Query<ProfileCategoryEntity>().ToList();
// Update all profile versions to the current version, profile migrations don't apply to LiteDB so anything loadable is assumed to be up to date
foreach (ProfileCategoryEntity profileCategoryEntity in categories)
UpdateProfileVersions(profileCategoryEntity);
return categories;
}
public ProfileCategoryEntity? Get(Guid id)
{
return _repository.FirstOrDefault<ProfileCategoryEntity>(p => p.Id == id);
ProfileCategoryEntity? result = _repository.FirstOrDefault<ProfileCategoryEntity>(p => p.Id == id);
if (result == null)
return null;
// Update all profile versions to the current version, profile migrations don't apply to LiteDB so anything loadable is assumed to be up to date
UpdateProfileVersions(result);
return result;
}
public ProfileCategoryEntity IsUnique(string name, Guid? id)
@ -72,4 +84,10 @@ internal class ProfileCategoryRepository : IProfileCategoryRepository
_profileIcons.Upload(profileConfigurationEntity.FileIconId, profileConfigurationEntity.FileIconId + ".png", stream);
}
private static void UpdateProfileVersions(ProfileCategoryEntity profileCategoryEntity)
{
foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations)
profileConfigurationEntity.Version = StorageMigrationService.PROFILE_VERSION;
}
}

View File

@ -9,6 +9,8 @@ namespace Artemis.Storage;
public class StorageMigrationService
{
public const int PROFILE_VERSION = 3;
private readonly ILogger _logger;
private readonly IList<IStorageMigration> _migrations;
private readonly LiteRepository _repository;

View File

@ -1,7 +1,8 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Artemis.Core;
using Artemis.UI.Shared.DataModelVisualization;
using Newtonsoft.Json;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
@ -11,16 +12,15 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
/// </summary>
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
{
private readonly JsonSerializerSettings _serializerSettings;
private readonly JsonSerializerOptions _serializerSettings;
private string _display;
public DefaultDataModelDisplayViewModel()
{
_serializerSettings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.None
};
_serializerSettings = CoreJson.GetJsonSerializerOptions();
_serializerSettings.ReferenceHandler = ReferenceHandler.IgnoreCycles;
_serializerSettings.WriteIndented = true;
_display = "null";
}
@ -35,7 +35,7 @@ internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<obje
if (DisplayValue is Enum enumDisplayValue)
Display = EnumUtilities.HumanizeValue(enumDisplayValue);
else if (DisplayValue is not string)
Display = JsonConvert.SerializeObject(DisplayValue, _serializerSettings);
Display = JsonSerializer.Serialize(DisplayValue, _serializerSettings);
else
Display = DisplayValue?.ToString() ?? "null";
}

View File

@ -24,6 +24,6 @@ public static class ClipboardExtensions
public static async Task<T?> GetJsonAsync<T>(this IClipboard clipboard, string format)
{
byte[]? bytes = (byte[]?) await clipboard.GetDataAsync(format);
return bytes == null ? default : CoreJson.DeserializeObject<T>(Encoding.Unicode.GetString(bytes), true);
return bytes == null ? default : CoreJson.Deserialize<T>(Encoding.Unicode.GetString(bytes).TrimEnd('\0'));
}
}

View File

@ -32,12 +32,12 @@ public class ResetLayerProperty<T> : IProfileEditorCommand
/// <inheritdoc />
public void Execute()
{
string json = CoreJson.SerializeObject(_layerProperty.DefaultValue, true);
string json = CoreJson.Serialize(_layerProperty.DefaultValue);
if (_keyframesEnabled)
_layerProperty.KeyframesEnabled = false;
_layerProperty.SetCurrentValue(CoreJson.DeserializeObject<T>(json)!);
_layerProperty.SetCurrentValue(CoreJson.Deserialize<T>(json)!);
}
/// <inheritdoc />

View File

@ -1,10 +1,7 @@
using System;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using Artemis.UI.Models;
using Avalonia;
using Artemis.UI.Shared.Extensions;
using Avalonia.Input;
namespace Artemis.UI.Extensions;
@ -19,7 +16,7 @@ public static class ProfileElementExtensions
public static async Task CopyToClipboard(this Folder folder)
{
DataObject dataObject = new();
string copy = CoreJson.SerializeObject(new FolderClipboardModel(folder), true);
string copy = CoreJson.Serialize(new FolderClipboardModel(folder));
dataObject.Set(ClipboardDataFormat, copy);
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
}
@ -27,7 +24,7 @@ public static class ProfileElementExtensions
public static async Task CopyToClipboard(this Layer layer)
{
DataObject dataObject = new();
string copy = CoreJson.SerializeObject(layer.LayerEntity, true);
string copy = CoreJson.Serialize(new LayerClipboardModel(layer));
dataObject.Set(ClipboardDataFormat, copy);
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
}
@ -35,18 +32,13 @@ public static class ProfileElementExtensions
public static async Task<RenderProfileElement?> 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), true);
IClipboardModel? entity = await Shared.UI.Clipboard.GetJsonAsync<IClipboardModel>(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;
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
@ -7,7 +8,7 @@ using Artemis.UI.Exceptions;
namespace Artemis.UI.Models;
public class FolderClipboardModel
public class FolderClipboardModel: IClipboardModel
{
public FolderClipboardModel(Folder folder)
{
@ -20,7 +21,7 @@ public class FolderClipboardModel
Layers.Add(allLayer.LayerEntity);
}
// ReSharper disable once UnusedMember.Global - For JSON.NET
[JsonConstructor]
public FolderClipboardModel()
{
FolderEntity = null;

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace Artemis.UI.Models;
[JsonDerivedType(typeof(LayerClipboardModel), "ClipboardLayer")]
[JsonDerivedType(typeof(FolderClipboardModel), "ClipboardFolder")]
[JsonDerivedType(typeof(KeyframeClipboardModel), "ClipboardKeyframe")]
[JsonDerivedType(typeof(NodesClipboardModel), "ClipboardNodes")]
public interface IClipboardModel
{
}

View File

@ -1,10 +1,10 @@
using Artemis.Core;
using System.Text.Json.Serialization;
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
namespace Artemis.UI.Models;
public class KeyframeClipboardModel
public class KeyframeClipboardModel: IClipboardModel
{
public const string ClipboardDataFormat = "Artemis.Keyframes";

View File

@ -0,0 +1,27 @@
using System;
using System.Text.Json.Serialization;
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
namespace Artemis.UI.Models;
public class LayerClipboardModel : IClipboardModel
{
public LayerClipboardModel(Layer layer)
{
Layer = layer.LayerEntity;
}
[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);
}
}

View File

@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Artemis.Core;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.UI.Models;
public class NodesClipboardModel
public class NodesClipboardModel: IClipboardModel
{
public NodesClipboardModel(NodeScript nodeScript, List<INode> nodes)
{
@ -18,6 +19,7 @@ public class NodesClipboardModel
Connections = nodeScript.Entity.Connections.Where(e => nodes.Any(n => n.Id == e.SourceNode) && nodes.Any(n => n.Id == e.TargetNode)).ToList();
}
[JsonConstructor]
public NodesClipboardModel()
{
Nodes = new List<NodeEntity>();

View File

@ -1,15 +1,14 @@
using System.Text.Json;
using System.Threading;
using Artemis.UI.Extensions;
using Artemis.UI.Shared;
using Artemis.WebClient.Workshop.Services;
using Newtonsoft.Json;
using PropertyChanged.SourceGenerator;
namespace Artemis.UI.Screens.Debugger.Workshop;
public partial class WorkshopDebugViewModel : ActivatableViewModelBase
{
[Notify] private string? _token;
[Notify] private bool _emailVerified;
[Notify] private string? _claims;
@ -18,12 +17,12 @@ public partial class WorkshopDebugViewModel : ActivatableViewModelBase
public WorkshopDebugViewModel(IWorkshopService workshopService, IAuthenticationService authenticationService)
{
DisplayName = "Workshop";
this.WhenActivatedAsync(async _ =>
{
Token = await authenticationService.GetBearer();
EmailVerified = authenticationService.GetIsEmailVerified();
Claims = JsonConvert.SerializeObject(authenticationService.Claims, Formatting.Indented);
Claims = JsonSerializer.Serialize(authenticationService.Claims, new JsonSerializerOptions {WriteIndented = true});
WorkshopStatus = await workshopService.GetWorkshopStatus(CancellationToken.None);
});
}

View File

@ -14,7 +14,6 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
using Newtonsoft.Json;
using PropertyChanged.SourceGenerator;
using ReactiveUI;

View File

@ -6,6 +6,7 @@ using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Extensions;
using Artemis.UI.Models;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
@ -36,11 +37,9 @@ public class FolderTreeItemViewModel : TreeItemViewModel
{
await ProfileEditorService.SaveProfileAsync();
FolderEntity copy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(Folder.FolderEntity, true), true)!;
copy.Id = Guid.NewGuid();
copy.Name = Folder.Parent.GetNewFolderName(copy.Name + " - copy");
FolderClipboardModel copy = CoreJson.Deserialize<FolderClipboardModel>(CoreJson.Serialize(new FolderClipboardModel(Folder)))!;
Folder copied = copy.Paste(Folder.Profile, Folder.Parent);
Folder copied = new(Folder.Profile, Folder.Parent, copy);
ProfileEditorService.ExecuteCommand(new AddProfileElement(copied, Folder.Parent, Folder.Order - 1));
}

View File

@ -37,7 +37,7 @@ public class LayerTreeItemViewModel : TreeItemViewModel
{
await ProfileEditorService.SaveProfileAsync();
LayerEntity copy = CoreJson.DeserializeObject<LayerEntity>(CoreJson.SerializeObject(Layer.LayerEntity, true), true)!;
LayerEntity copy = CoreJson.Deserialize<LayerEntity>(CoreJson.Serialize(Layer.LayerEntity))!;
copy.Id = Guid.NewGuid();
copy.Name = Layer.Parent.GetNewFolderName(copy.Name + " - copy");

View File

@ -135,7 +135,7 @@ public partial class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, IT
else
keyframes.AddRange(_profileEditorService.SelectedKeyframes.Select(k => new KeyframeClipboardModel(k)));
string copy = CoreJson.SerializeObject(keyframes, true);
string copy = CoreJson.Serialize(keyframes);
DataObject dataObject = new();
dataObject.Set(KeyframeClipboardModel.ClipboardDataFormat, copy);
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);

View File

@ -2,12 +2,10 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
@ -18,7 +16,6 @@ using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using DynamicData;
using DynamicData.Binding;
using Newtonsoft.Json;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
@ -151,7 +148,7 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
private async Task ExecuteImportProfile()
{
string[]? result = await _windowService.CreateOpenFileDialog()
.HavingFilter(f => f.WithExtension("zip").WithExtension("json").WithName("Artemis profile"))
.HavingFilter(f => f.WithExtension("zip").WithName("Artemis profile"))
.ShowAsync();
if (result == null)
@ -159,24 +156,8 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
try
{
// Removing this at some point in the future
if (result[0].EndsWith("json"))
{
ProfileConfigurationExportModel? exportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(await File.ReadAllTextAsync(result[0]), IProfileService.ExportSettings);
if (exportModel == null)
{
await _windowService.ShowConfirmContentDialog("Import profile", "Failed to import this profile, make sure it is a valid Artemis profile.", "Confirm", null);
return;
}
await using Stream convertedFileStream = await ConvertLegacyExport(exportModel);
await _profileService.ImportProfile(convertedFileStream, ProfileCategory, true, true);
}
else
{
await using FileStream fileStream = File.OpenRead(result[0]);
await _profileService.ImportProfile(fileStream, ProfileCategory, true, true);
}
await using FileStream fileStream = File.OpenRead(result[0]);
await _profileService.ImportProfile(fileStream, ProfileCategory, true, true);
}
catch (Exception e)
{
@ -230,38 +211,4 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
_profileService.SaveProfileCategory(categories[i]);
}
}
private async Task<Stream> ConvertLegacyExport(ProfileConfigurationExportModel exportModel)
{
MemoryStream archiveStream = new();
string configurationJson = JsonConvert.SerializeObject(exportModel.ProfileConfigurationEntity, IProfileService.ExportSettings);
string profileJson = JsonConvert.SerializeObject(exportModel.ProfileEntity, IProfileService.ExportSettings);
// Create a ZIP archive
using (ZipArchive archive = new(archiveStream, ZipArchiveMode.Create, true))
{
ZipArchiveEntry configurationEntry = archive.CreateEntry("configuration.json");
await using (Stream entryStream = configurationEntry.Open())
{
await entryStream.WriteAsync(Encoding.Default.GetBytes(configurationJson));
}
ZipArchiveEntry profileEntry = archive.CreateEntry("profile.json");
await using (Stream entryStream = profileEntry.Open())
{
await entryStream.WriteAsync(Encoding.Default.GetBytes(profileJson));
}
if (exportModel.ProfileImage != null)
{
ZipArchiveEntry iconEntry = archive.CreateEntry("icon.png");
await using Stream entryStream = iconEntry.Open();
await exportModel.ProfileImage.CopyToAsync(entryStream);
}
}
archiveStream.Seek(0, SeekOrigin.Begin);
return archiveStream;
}
}

View File

@ -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<INode> 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), true);
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<NodesClipboardModel>(Encoding.Unicode.GetString(bytes), true);
NodesClipboardModel? nodesClipboardModel = await Shared.UI.Clipboard.GetJsonAsync<NodesClipboardModel>(CLIPBOARD_DATA_FORMAT);
if (nodesClipboardModel == null)
return;

View File

@ -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<PluginInfo>(reader.ReadToEnd())!;
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
if (!pluginInfo.Main.EndsWith(".dll"))
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");

View File

@ -1,10 +1,9 @@
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Artemis.Core;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.Entities;
using Artemis.WebClient.Workshop.Exceptions;
using Newtonsoft.Json;
using RGB.NET.Layout;
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
@ -70,7 +69,7 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler
if (!response.IsSuccessStatusCode)
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
Release? release = JsonConvert.DeserializeObject<Release>(await response.Content.ReadAsStringAsync(cancellationToken));
Release? release = await response.Content.ReadFromJsonAsync<Release>(cancellationToken);
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
}

View File

@ -1,6 +1,6 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Artemis.WebClient.Workshop.Entities;
using Newtonsoft.Json;
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 = JsonConvert.DeserializeObject<Release>(await response.Content.ReadAsStringAsync(cancellationToken));
Release? release = await response.Content.ReadFromJsonAsync<Release>(cancellationToken);
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
}
}

View File

@ -1,8 +1,7 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Artemis.Core.Services;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.Entities;
using Newtonsoft.Json;
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
@ -39,7 +38,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler
if (!response.IsSuccessStatusCode)
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
Release? release = JsonConvert.DeserializeObject<Release>(await response.Content.ReadAsStringAsync(cancellationToken));
Release? release = await response.Content.ReadFromJsonAsync<Release>(cancellationToken);
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
}
}

View File

@ -33,7 +33,6 @@
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageVersion Include="Microsoft.Win32" Version="2.0.1" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="8.0.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NoStringEvaluating" Version="2.5.2" />
<PackageVersion Include="Octopus.Octodiff" Version="2.0.546" />
<PackageVersion Include="PropertyChanged.SourceGenerator" Version="1.1.0" />
@ -52,6 +51,7 @@
<PackageVersion Include="Splat.DryIoc" Version="14.8.12" />
<PackageVersion Include="StrawberryShake.Server" Version="13.9.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
<PackageVersion Include="System.Text.Json" Version="8.0.2" />
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.56" />
</ItemGroup>
</Project>