mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-31 17:53:32 +00:00
Fixed the runtime errors I've run in to so far
This commit is contained in:
parent
6d8572cce0
commit
68826583ab
@ -92,14 +92,6 @@ public static class Constants
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
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()}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A read-only collection containing all primitive numeric types
|
/// A read-only collection containing all primitive numeric types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -147,4 +139,8 @@ public static class Constants
|
|||||||
/// Gets the startup arguments provided to the application
|
/// Gets the startup arguments provided to the application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
|
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()}};
|
||||||
}
|
}
|
||||||
@ -1,34 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Artemis.Core.JsonConverters
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An int converter that, if required, will round float values
|
|
||||||
/// </summary>
|
|
||||||
internal class ForgivingIntConverter : JsonConverter<int>
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -164,7 +164,7 @@ public sealed class Folder : RenderProfileElement
|
|||||||
if (Parent == null)
|
if (Parent == null)
|
||||||
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
|
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
|
||||||
|
|
||||||
FolderEntity entityCopy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(FolderEntity))!;
|
FolderEntity entityCopy = CoreJson.Deserialize<FolderEntity>(CoreJson.Serialize(FolderEntity))!;
|
||||||
entityCopy.Id = Guid.NewGuid();
|
entityCopy.Id = Guid.NewGuid();
|
||||||
entityCopy.Name += " - Copy";
|
entityCopy.Name += " - Copy";
|
||||||
|
|
||||||
|
|||||||
@ -324,8 +324,8 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
|||||||
// Reference types make a deep clone (ab)using JSON
|
// Reference types make a deep clone (ab)using JSON
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string json = CoreJson.SerializeObject(DefaultValue);
|
string json = CoreJson.Serialize(DefaultValue);
|
||||||
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!);
|
SetCurrentValue(CoreJson.Deserialize<T>(json)!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,7 +420,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
T? value = CoreJson.DeserializeObject<T>(keyframeEntity.Value);
|
T? value = CoreJson.Deserialize<T>(keyframeEntity.Value);
|
||||||
if (value == null)
|
if (value == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -625,7 +625,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Entity.Value != null)
|
if (Entity.Value != null)
|
||||||
BaseValue = CoreJson.DeserializeObject<T>(Entity.Value)!;
|
BaseValue = CoreJson.Deserialize<T>(Entity.Value)!;
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
@ -664,7 +664,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
|||||||
if (!_isInitialized)
|
if (!_isInitialized)
|
||||||
throw new ArtemisCoreException("Layer property is not yet initialized");
|
throw new ArtemisCoreException("Layer property is not yet initialized");
|
||||||
|
|
||||||
Entity.Value = CoreJson.SerializeObject(BaseValue);
|
Entity.Value = CoreJson.Serialize(BaseValue);
|
||||||
Entity.KeyframesEnabled = KeyframesEnabled;
|
Entity.KeyframesEnabled = KeyframesEnabled;
|
||||||
Entity.KeyframeEntities.Clear();
|
Entity.KeyframeEntities.Clear();
|
||||||
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
|
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
|
||||||
|
|||||||
@ -69,7 +69,7 @@ public class LayerPropertyKeyframe<T> : CorePropertyChanged, ILayerPropertyKeyfr
|
|||||||
{
|
{
|
||||||
return new KeyframeEntity
|
return new KeyframeEntity
|
||||||
{
|
{
|
||||||
Value = CoreJson.SerializeObject(Value),
|
Value = CoreJson.Serialize(Value),
|
||||||
Position = Position,
|
Position = Position,
|
||||||
EasingFunction = (int) EasingFunction
|
EasingFunction = (int) EasingFunction
|
||||||
};
|
};
|
||||||
|
|||||||
@ -29,6 +29,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
private Uri? _license;
|
private Uri? _license;
|
||||||
private string? _licenseName;
|
private string? _licenseName;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
internal PluginInfo()
|
internal PluginInfo()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -37,6 +38,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// The plugins GUID
|
/// The plugins GUID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
[JsonInclude]
|
||||||
public Guid Guid
|
public Guid Guid
|
||||||
{
|
{
|
||||||
get => _guid;
|
get => _guid;
|
||||||
@ -47,6 +49,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// The name of the plugin
|
/// The name of the plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
[JsonInclude]
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get => _name;
|
get => _name;
|
||||||
@ -130,6 +133,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// The version of the plugin
|
/// The version of the plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
[JsonInclude]
|
||||||
public string Version
|
public string Version
|
||||||
{
|
{
|
||||||
get => _version;
|
get => _version;
|
||||||
@ -140,6 +144,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// The main entry DLL, should contain a class implementing Plugin
|
/// The main entry DLL, should contain a class implementing Plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
|
[JsonInclude]
|
||||||
public string Main
|
public string Main
|
||||||
{
|
{
|
||||||
get => _main;
|
get => _main;
|
||||||
@ -159,6 +164,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a boolean indicating whether this plugin requires elevated admin privileges
|
/// Gets a boolean indicating whether this plugin requires elevated admin privileges
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonInclude]
|
||||||
public bool RequiresAdmin
|
public bool RequiresAdmin
|
||||||
{
|
{
|
||||||
get => _requiresAdmin;
|
get => _requiresAdmin;
|
||||||
@ -177,6 +183,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets
|
/// Gets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonInclude]
|
||||||
public PluginPlatform? Platforms
|
public PluginPlatform? Platforms
|
||||||
{
|
{
|
||||||
get => _platforms;
|
get => _platforms;
|
||||||
@ -186,6 +193,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the API version the plugin was built for
|
/// Gets the API version the plugin was built for
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonInclude]
|
||||||
public Version? Api
|
public Version? Api
|
||||||
{
|
{
|
||||||
get => _api;
|
get => _api;
|
||||||
@ -195,6 +203,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin this info is associated with
|
/// Gets the plugin this info is associated with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public Plugin Plugin
|
public Plugin Plugin
|
||||||
{
|
{
|
||||||
get => _plugin;
|
get => _plugin;
|
||||||
@ -204,6 +213,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a string representing either a full path pointing to an svg or the markdown icon
|
/// Gets a string representing either a full path pointing to an svg or the markdown icon
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public string? ResolvedIcon
|
public string? ResolvedIcon
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -228,9 +238,11 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
[JsonIgnore]
|
||||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
[JsonIgnore]
|
||||||
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
|
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
|
|||||||
Name = pluginSettingEntity.Name;
|
Name = pluginSettingEntity.Name;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_value = CoreJson.DeserializeObject<T>(pluginSettingEntity.Value)!;
|
_value = CoreJson.Deserialize<T>(pluginSettingEntity.Value)!;
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
@ -76,7 +76,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
|
|||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool HasChanged => CoreJson.SerializeObject(Value) != _pluginSettingEntity.Value;
|
public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool AutoSave { get; set; }
|
public bool AutoSave { get; set; }
|
||||||
@ -84,7 +84,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void RejectChanges()
|
public void RejectChanges()
|
||||||
{
|
{
|
||||||
Value = CoreJson.DeserializeObject<T>(_pluginSettingEntity.Value);
|
Value = CoreJson.Deserialize<T>(_pluginSettingEntity.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -93,7 +93,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
|
|||||||
if (!HasChanged)
|
if (!HasChanged)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_pluginSettingEntity.Value = CoreJson.SerializeObject(Value);
|
_pluginSettingEntity.Value = CoreJson.Serialize(Value);
|
||||||
_pluginRepository.SaveSetting(_pluginSettingEntity);
|
_pluginRepository.SaveSetting(_pluginSettingEntity);
|
||||||
OnSettingSaved();
|
OnSettingSaved();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@ public class PluginSettings
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
PluginGuid = Plugin.Guid,
|
PluginGuid = Plugin.Guid,
|
||||||
Value = CoreJson.SerializeObject(defaultValue)
|
Value = CoreJson.Serialize(defaultValue)
|
||||||
};
|
};
|
||||||
_pluginRepository.AddSetting(settingEntity);
|
_pluginRepository.AddSetting(settingEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,12 +41,12 @@ internal class NodeService : INodeService
|
|||||||
public string ExportScript(NodeScript nodeScript)
|
public string ExportScript(NodeScript nodeScript)
|
||||||
{
|
{
|
||||||
nodeScript.Save();
|
nodeScript.Save();
|
||||||
return CoreJson.SerializeObject(nodeScript.Entity);
|
return CoreJson.Serialize(nodeScript.Entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ImportScript(string json, NodeScript target)
|
public void ImportScript(string json, NodeScript target)
|
||||||
{
|
{
|
||||||
NodeScriptEntity? entity = CoreJson.DeserializeObject<NodeScriptEntity>(json);
|
NodeScriptEntity? entity = CoreJson.Deserialize<NodeScriptEntity>(json);
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
throw new ArtemisCoreException("Failed to load node script");
|
throw new ArtemisCoreException("Failed to load node script");
|
||||||
|
|
||||||
|
|||||||
@ -127,7 +127,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName);
|
throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName);
|
||||||
|
|
||||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
using StreamReader reader = new(metaDataFileEntry.Open());
|
||||||
PluginInfo builtInPluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
PluginInfo builtInPluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
|
||||||
string preferred = builtInPluginInfo.PreferredPluginDirectory;
|
string preferred = builtInPluginInfo.PreferredPluginDirectory;
|
||||||
|
|
||||||
// Find the matching plugin in the plugin folder
|
// 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");
|
_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 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)
|
if (!pluginInfo.IsCompatible)
|
||||||
return null;
|
return null;
|
||||||
@ -610,7 +610,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
throw new ArtemisPluginException("Couldn't find a plugin.json in " + fileName);
|
throw new ArtemisPluginException("Couldn't find a plugin.json in " + fileName);
|
||||||
|
|
||||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
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"))
|
if (!pluginInfo.Main.EndsWith(".dll"))
|
||||||
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
|
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
|
||||||
|
|
||||||
|
|||||||
@ -410,8 +410,8 @@ internal class ProfileService : IProfileService
|
|||||||
if (profileEntity == null)
|
if (profileEntity == null)
|
||||||
throw new ArtemisCoreException("Could not locate profile entity");
|
throw new ArtemisCoreException("Could not locate profile entity");
|
||||||
|
|
||||||
string configurationJson = CoreJson.SerializeObject(profileConfiguration.Entity);
|
string configurationJson = CoreJson.Serialize(profileConfiguration.Entity);
|
||||||
string profileJson = CoreJson.SerializeObject(profileEntity);
|
string profileJson = CoreJson.Serialize(profileEntity);
|
||||||
|
|
||||||
MemoryStream archiveStream = new();
|
MemoryStream archiveStream = new();
|
||||||
|
|
||||||
@ -461,11 +461,11 @@ internal class ProfileService : IProfileService
|
|||||||
// Deserialize profile configuration to JObject
|
// Deserialize profile configuration to JObject
|
||||||
await using Stream configurationStream = configurationEntry.Open();
|
await using Stream configurationStream = configurationEntry.Open();
|
||||||
using StreamReader configurationReader = new(configurationStream);
|
using StreamReader configurationReader = new(configurationStream);
|
||||||
JsonObject? configurationJson = CoreJson.DeserializeObject<JsonObject>(await configurationReader.ReadToEndAsync());
|
JsonObject? configurationJson = CoreJson.Deserialize<JsonObject>(await configurationReader.ReadToEndAsync());
|
||||||
// Deserialize profile to JObject
|
// Deserialize profile to JObject
|
||||||
await using Stream profileStream = profileEntry.Open();
|
await using Stream profileStream = profileEntry.Open();
|
||||||
using StreamReader profileReader = new(profileStream);
|
using StreamReader profileReader = new(profileStream);
|
||||||
JsonObject? profileJson = CoreJson.DeserializeObject<JsonObject>(await profileReader.ReadToEndAsync());
|
JsonObject? profileJson = CoreJson.Deserialize<JsonObject>(await profileReader.ReadToEndAsync());
|
||||||
|
|
||||||
// Before deserializing, apply any pending migrations
|
// Before deserializing, apply any pending migrations
|
||||||
MigrateProfile(configurationJson, profileJson);
|
MigrateProfile(configurationJson, profileJson);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
|
||||||
|
/// <see cref="object" /> or <see langword="null" />.
|
||||||
|
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
|
||||||
|
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
|
||||||
|
/// JSON throws a <see cref="JsonException" />.
|
||||||
|
/// </summary>
|
||||||
|
public bool ThrowOnFail { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<T>(await reader.ReadToEndAsync());
|
||||||
|
if (dataModel != null)
|
||||||
|
_update(dataModel, _module.DataModel);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
if (ThrowOnFail)
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,6 +44,16 @@ public interface IWebServerService : IArtemisService
|
|||||||
/// <returns>The resulting end point</returns>
|
/// <returns>The resulting end point</returns>
|
||||||
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
|
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>
|
||||||
|
/// <typeparam name="T">The data model type of the module</typeparam>
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -4,8 +4,10 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
using EmbedIO;
|
using EmbedIO;
|
||||||
using EmbedIO.WebApi;
|
using EmbedIO.WebApi;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@ -21,7 +23,7 @@ internal class WebServerService : IWebServerService, IDisposable
|
|||||||
private readonly PluginSetting<bool> _webServerEnabledSetting;
|
private readonly PluginSetting<bool> _webServerEnabledSetting;
|
||||||
private readonly PluginSetting<int> _webServerPortSetting;
|
private readonly PluginSetting<int> _webServerPortSetting;
|
||||||
private readonly object _webserverLock = new();
|
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;
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
|
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
|
||||||
@ -239,7 +241,17 @@ internal class WebServerService : IWebServerService, IDisposable
|
|||||||
PluginsModule.AddPluginEndPoint(endPoint);
|
PluginsModule.AddPluginEndPoint(endPoint);
|
||||||
return endPoint;
|
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));
|
||||||
|
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||||
|
DataModelJsonPluginEndPoint<T> endPoint = new(module, endPointName, PluginsModule);
|
||||||
|
PluginsModule.AddPluginEndPoint(endPoint);
|
||||||
|
return endPoint;
|
||||||
|
}
|
||||||
|
|
||||||
public void RemovePluginEndPoint(PluginEndPoint endPoint)
|
public void RemovePluginEndPoint(PluginEndPoint endPoint)
|
||||||
{
|
{
|
||||||
PluginsModule.RemovePluginEndPoint(endPoint);
|
PluginsModule.RemovePluginEndPoint(endPoint);
|
||||||
@ -307,7 +319,7 @@ internal class WebServerService : IWebServerService, IDisposable
|
|||||||
context.Response.ContentType = MimeType.Json;
|
context.Response.ContentType = MimeType.Json;
|
||||||
await using TextWriter writer = context.OpenResponseText();
|
await using TextWriter writer = context.OpenResponseText();
|
||||||
|
|
||||||
string response = CoreJson.SerializeObject(new Dictionary<string, object?>
|
string response = CoreJson.Serialize(new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
{"StatusCode", context.Response.StatusCode},
|
{"StatusCode", context.Response.StatusCode},
|
||||||
{"StackTrace", exception.StackTrace},
|
{"StackTrace", exception.StackTrace},
|
||||||
|
|||||||
@ -9,22 +9,16 @@ namespace Artemis.Core;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CoreJson
|
public static class CoreJson
|
||||||
{
|
{
|
||||||
#region Serialize
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes the specified object to a JSON string.
|
/// Serializes the specified object to a JSON string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The object to serialize.</param>
|
/// <param name="value">The object to serialize.</param>
|
||||||
/// <returns>A JSON string representation of the object.</returns>
|
/// <returns>A JSON string representation of the object.</returns>
|
||||||
[DebuggerStepThrough]
|
[DebuggerStepThrough]
|
||||||
public static string SerializeObject(object? value)
|
public static string Serialize(object? value)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(value, Constants.JsonConvertSettings);
|
return JsonSerializer.Serialize(value, Constants.JsonConvertSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Deserialize
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserializes the JSON to the specified .NET type.
|
/// Deserializes the JSON to the specified .NET type.
|
||||||
@ -34,10 +28,17 @@ public static class CoreJson
|
|||||||
/// <returns>The deserialized object from the JSON string.</returns>
|
/// <returns>The deserialized object from the JSON string.</returns>
|
||||||
[DebuggerStepThrough]
|
[DebuggerStepThrough]
|
||||||
[return: MaybeNull]
|
[return: MaybeNull]
|
||||||
public static T DeserializeObject<T>(string value)
|
public static T Deserialize<T>(string value)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<T>(value, Constants.JsonConvertSettings);
|
return JsonSerializer.Deserialize<T>(value, Constants.JsonConvertSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -41,12 +41,12 @@ public abstract class Node<TStorage> : Node
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string SerializeStorage()
|
public override string SerializeStorage()
|
||||||
{
|
{
|
||||||
return CoreJson.SerializeObject(Storage);
|
return CoreJson.Serialize(Storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void DeserializeStorage(string serialized)
|
public override void DeserializeStorage(string serialized)
|
||||||
{
|
{
|
||||||
Storage = CoreJson.DeserializeObject<TStorage>(serialized) ?? default(TStorage);
|
Storage = CoreJson.Deserialize<TStorage>(serialized) ?? default(TStorage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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;
|
public interface IAdaptionHintEntity;
|
||||||
@ -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;
|
public interface IConditionEntity;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Storage.Entities.Profile.Nodes;
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
|
using Serilog.Core;
|
||||||
|
|
||||||
namespace Artemis.Storage.Entities.Profile;
|
namespace Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
@ -26,5 +27,5 @@ public class ProfileConfigurationEntity
|
|||||||
public Guid ProfileId { get; set; }
|
public Guid ProfileId { get; set; }
|
||||||
|
|
||||||
public bool FadeInAndOut { get; set; }
|
public bool FadeInAndOut { get; set; }
|
||||||
public int Version { get; set; }
|
public int Version { get; set; } = StorageMigrationService.PROFILE_VERSION;
|
||||||
}
|
}
|
||||||
@ -1,7 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using Serilog.Core;
|
|
||||||
|
|
||||||
namespace Artemis.Storage.Migrations.Profile
|
namespace Artemis.Storage.Migrations.Profile
|
||||||
{
|
{
|
||||||
@ -17,34 +14,37 @@ namespace Artemis.Storage.Migrations.Profile
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Migrate(JsonObject configurationJson, JsonObject profileJson)
|
public void Migrate(JsonObject configurationJson, JsonObject profileJson)
|
||||||
{
|
{
|
||||||
JsonArray? folders = (JsonArray?) profileJson["Folders"]?["values"];
|
JsonArray? folders = profileJson["Folders"]?["$values"]?.AsArray();
|
||||||
JsonArray? layers = (JsonArray?) profileJson["Layers"]?["values"];
|
JsonArray? layers = profileJson["Layers"]?["$values"]?.AsArray();
|
||||||
|
|
||||||
if (folders != null)
|
if (folders != null)
|
||||||
{
|
{
|
||||||
foreach (JsonValue folder in folders)
|
foreach (JsonNode? folder in folders)
|
||||||
MigrateProfileElement(folder);
|
MigrateProfileElement(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layers != null)
|
if (layers != null)
|
||||||
{
|
{
|
||||||
foreach (JsonValue layer in layers)
|
foreach (JsonNode? layer in layers)
|
||||||
{
|
{
|
||||||
MigrateProfileElement(layer);
|
MigrateProfileElement(layer);
|
||||||
MigratePropertyGroup(layer["GeneralPropertyGroup"]);
|
MigratePropertyGroup(layer?["GeneralPropertyGroup"]);
|
||||||
MigratePropertyGroup(layer["TransformPropertyGroup"]);
|
MigratePropertyGroup(layer?["TransformPropertyGroup"]);
|
||||||
MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]);
|
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)
|
if (layerEffects != null)
|
||||||
{
|
{
|
||||||
foreach (JsonValue layerEffect in layerEffects)
|
foreach (JsonNode? layerEffect in layerEffects)
|
||||||
MigratePropertyGroup(layerEffect["PropertyGroup"]);
|
MigratePropertyGroup(layerEffect?["PropertyGroup"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode? displayCondition = profileElement["DisplayCondition"];
|
JsonNode? displayCondition = profileElement["DisplayCondition"];
|
||||||
@ -57,28 +57,24 @@ namespace Artemis.Storage.Migrations.Profile
|
|||||||
if (propertyGroup == null)
|
if (propertyGroup == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
JsonArray? properties = (JsonArray?) propertyGroup["Properties"]?["values"];
|
JsonArray? properties = propertyGroup["Properties"]?["$values"]?.AsArray();
|
||||||
JsonArray? propertyGroups = (JsonArray?) propertyGroup["PropertyGroups"]?["values"];
|
JsonArray? propertyGroups = propertyGroup["PropertyGroups"]?["$values"]?.AsArray();
|
||||||
|
|
||||||
if (properties != null)
|
if (properties != null)
|
||||||
{
|
{
|
||||||
foreach (JsonValue property in properties)
|
foreach (JsonNode? property in properties)
|
||||||
MigrateNodeScript(property["DataBinding"]?["NodeScript"]);
|
MigrateNodeScript(property?["DataBinding"]?["NodeScript"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyGroups != null)
|
if (propertyGroups != null)
|
||||||
{
|
{
|
||||||
foreach (JsonValue childPropertyGroup in propertyGroups)
|
foreach (JsonNode? childPropertyGroup in propertyGroups)
|
||||||
MigratePropertyGroup(childPropertyGroup);
|
MigratePropertyGroup(childPropertyGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MigrateNodeScript(JsonNode? nodeScript)
|
private void MigrateNodeScript(JsonNode? nodeScript)
|
||||||
{
|
{
|
||||||
if (nodeScript == null)
|
JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray();
|
||||||
return;
|
|
||||||
|
|
||||||
JsonArray? nodes = nodeScript["Nodes"]?.AsArray();
|
|
||||||
if (nodes == null)
|
if (nodes == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -16,13 +16,10 @@ internal class M0002NodeProvidersProfileConfig : IProfileMigration
|
|||||||
{
|
{
|
||||||
MigrateNodeScript(configurationJson["ActivationCondition"]);
|
MigrateNodeScript(configurationJson["ActivationCondition"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MigrateNodeScript(JsonNode? nodeScript)
|
private void MigrateNodeScript(JsonNode? nodeScript)
|
||||||
{
|
{
|
||||||
if (nodeScript == null)
|
JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray();
|
||||||
return;
|
|
||||||
|
|
||||||
JsonArray? nodes = nodeScript["Nodes"]?.AsArray();
|
|
||||||
if (nodes == null)
|
if (nodes == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
{
|
||||||
|
// 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<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
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,8 @@ namespace Artemis.Storage;
|
|||||||
|
|
||||||
public class StorageMigrationService
|
public class StorageMigrationService
|
||||||
{
|
{
|
||||||
|
public const int PROFILE_VERSION = 3;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IList<IStorageMigration> _migrations;
|
private readonly IList<IStorageMigration> _migrations;
|
||||||
private readonly LiteRepository _repository;
|
private readonly LiteRepository _repository;
|
||||||
|
|||||||
@ -17,7 +17,10 @@ internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<obje
|
|||||||
|
|
||||||
public DefaultDataModelDisplayViewModel()
|
public DefaultDataModelDisplayViewModel()
|
||||||
{
|
{
|
||||||
_serializerSettings = new JsonSerializerOptions() {ReferenceHandler = ReferenceHandler.IgnoreCycles};
|
_serializerSettings = CoreJson.GetJsonSerializerOptions();
|
||||||
|
_serializerSettings.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
||||||
|
_serializerSettings.WriteIndented = true;
|
||||||
|
|
||||||
_display = "null";
|
_display = "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,6 @@ public static class ClipboardExtensions
|
|||||||
public static async Task<T?> GetJsonAsync<T>(this IClipboard clipboard, string format)
|
public static async Task<T?> GetJsonAsync<T>(this IClipboard clipboard, string format)
|
||||||
{
|
{
|
||||||
byte[]? bytes = (byte[]?) await clipboard.GetDataAsync(format);
|
byte[]? bytes = (byte[]?) await clipboard.GetDataAsync(format);
|
||||||
return bytes == null ? default : CoreJson.DeserializeObject<T>(Encoding.Unicode.GetString(bytes));
|
return bytes == null ? default : CoreJson.Deserialize<T>(Encoding.Unicode.GetString(bytes).TrimEnd('\0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,12 +32,12 @@ public class ResetLayerProperty<T> : IProfileEditorCommand
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Execute()
|
public void Execute()
|
||||||
{
|
{
|
||||||
string json = CoreJson.SerializeObject(_layerProperty.DefaultValue);
|
string json = CoreJson.Serialize(_layerProperty.DefaultValue);
|
||||||
|
|
||||||
if (_keyframesEnabled)
|
if (_keyframesEnabled)
|
||||||
_layerProperty.KeyframesEnabled = false;
|
_layerProperty.KeyframesEnabled = false;
|
||||||
|
|
||||||
_layerProperty.SetCurrentValue(CoreJson.DeserializeObject<T>(json)!);
|
_layerProperty.SetCurrentValue(CoreJson.Deserialize<T>(json)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.UI.Models;
|
using Artemis.UI.Models;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ public static class ProfileElementExtensions
|
|||||||
public static async Task CopyToClipboard(this Folder folder)
|
public static async Task CopyToClipboard(this Folder folder)
|
||||||
{
|
{
|
||||||
DataObject dataObject = new();
|
DataObject dataObject = new();
|
||||||
string copy = CoreJson.SerializeObject(new FolderClipboardModel(folder));
|
string copy = CoreJson.Serialize(new FolderClipboardModel(folder));
|
||||||
dataObject.Set(ClipboardDataFormat, copy);
|
dataObject.Set(ClipboardDataFormat, copy);
|
||||||
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
||||||
}
|
}
|
||||||
@ -27,7 +28,7 @@ public static class ProfileElementExtensions
|
|||||||
public static async Task CopyToClipboard(this Layer layer)
|
public static async Task CopyToClipboard(this Layer layer)
|
||||||
{
|
{
|
||||||
DataObject dataObject = new();
|
DataObject dataObject = new();
|
||||||
string copy = CoreJson.SerializeObject(layer.LayerEntity);
|
string copy = CoreJson.Serialize(layer.LayerEntity);
|
||||||
dataObject.Set(ClipboardDataFormat, copy);
|
dataObject.Set(ClipboardDataFormat, copy);
|
||||||
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
||||||
}
|
}
|
||||||
@ -35,18 +36,13 @@ public static class ProfileElementExtensions
|
|||||||
|
|
||||||
public static async Task<RenderProfileElement?> PasteChildFromClipboard(this Folder parent)
|
public static async Task<RenderProfileElement?> PasteChildFromClipboard(this Folder parent)
|
||||||
{
|
{
|
||||||
byte[]? bytes = (byte[]?) await Shared.UI.Clipboard.GetDataAsync(ClipboardDataFormat);
|
IClipboardModel? entity = await Shared.UI.Clipboard.GetJsonAsync<IClipboardModel>(ClipboardDataFormat);
|
||||||
if (bytes == null!)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
object? entity = CoreJson.DeserializeObject<IClipboardModel>(Encoding.Unicode.GetString(bytes));
|
|
||||||
switch (entity)
|
switch (entity)
|
||||||
{
|
{
|
||||||
case FolderClipboardModel folderClipboardModel:
|
case FolderClipboardModel folderClipboardModel:
|
||||||
return folderClipboardModel.Paste(parent.Profile, parent);
|
return folderClipboardModel.Paste(parent.Profile, parent);
|
||||||
case LayerEntity layerEntity:
|
case LayerClipboardModel layerClipboardModel:
|
||||||
layerEntity.Id = Guid.NewGuid();
|
return layerClipboardModel.Paste(parent);
|
||||||
return new Layer(parent.Profile, parent, layerEntity, true);
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,10 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace Artemis.UI.Models;
|
namespace Artemis.UI.Models;
|
||||||
|
|
||||||
[JsonDerivedType(typeof(LayerClipboardModel))]
|
[JsonDerivedType(typeof(LayerClipboardModel), "ClipboardLayer")]
|
||||||
[JsonDerivedType(typeof(FolderClipboardModel))]
|
[JsonDerivedType(typeof(FolderClipboardModel), "ClipboardFolder")]
|
||||||
[JsonDerivedType(typeof(KeyframeClipboardModel))]
|
[JsonDerivedType(typeof(KeyframeClipboardModel), "ClipboardKeyframe")]
|
||||||
[JsonDerivedType(typeof(NodesClipboardModel))]
|
[JsonDerivedType(typeof(NodesClipboardModel), "ClipboardNodes")]
|
||||||
public interface IClipboardModel
|
public interface IClipboardModel
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -1,4 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
namespace Artemis.UI.Models;
|
namespace Artemis.UI.Models;
|
||||||
|
|
||||||
@ -6,8 +9,19 @@ public class LayerClipboardModel : IClipboardModel
|
|||||||
{
|
{
|
||||||
public LayerClipboardModel(Layer layer)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ public class FolderTreeItemViewModel : TreeItemViewModel
|
|||||||
{
|
{
|
||||||
await ProfileEditorService.SaveProfileAsync();
|
await ProfileEditorService.SaveProfileAsync();
|
||||||
|
|
||||||
FolderEntity copy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(Folder.FolderEntity))!;
|
FolderEntity copy = CoreJson.Deserialize<FolderEntity>(CoreJson.Serialize(Folder.FolderEntity))!;
|
||||||
copy.Id = Guid.NewGuid();
|
copy.Id = Guid.NewGuid();
|
||||||
copy.Name = Folder.Parent.GetNewFolderName(copy.Name + " - copy");
|
copy.Name = Folder.Parent.GetNewFolderName(copy.Name + " - copy");
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ public class LayerTreeItemViewModel : TreeItemViewModel
|
|||||||
{
|
{
|
||||||
await ProfileEditorService.SaveProfileAsync();
|
await ProfileEditorService.SaveProfileAsync();
|
||||||
|
|
||||||
LayerEntity copy = CoreJson.DeserializeObject<LayerEntity>(CoreJson.SerializeObject(Layer.LayerEntity))!;
|
LayerEntity copy = CoreJson.Deserialize<LayerEntity>(CoreJson.Serialize(Layer.LayerEntity))!;
|
||||||
copy.Id = Guid.NewGuid();
|
copy.Id = Guid.NewGuid();
|
||||||
copy.Name = Layer.Parent.GetNewFolderName(copy.Name + " - copy");
|
copy.Name = Layer.Parent.GetNewFolderName(copy.Name + " - copy");
|
||||||
|
|
||||||
|
|||||||
@ -135,7 +135,7 @@ public partial class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, IT
|
|||||||
else
|
else
|
||||||
keyframes.AddRange(_profileEditorService.SelectedKeyframes.Select(k => new KeyframeClipboardModel(k)));
|
keyframes.AddRange(_profileEditorService.SelectedKeyframes.Select(k => new KeyframeClipboardModel(k)));
|
||||||
|
|
||||||
string copy = CoreJson.SerializeObject(keyframes);
|
string copy = CoreJson.Serialize(keyframes);
|
||||||
DataObject dataObject = new();
|
DataObject dataObject = new();
|
||||||
dataObject.Set(KeyframeClipboardModel.ClipboardDataFormat, copy);
|
dataObject.Set(KeyframeClipboardModel.ClipboardDataFormat, copy);
|
||||||
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
||||||
|
|||||||
@ -161,7 +161,7 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
|
|||||||
// Removing this at some point in the future
|
// Removing this at some point in the future
|
||||||
if (result[0].EndsWith("json"))
|
if (result[0].EndsWith("json"))
|
||||||
{
|
{
|
||||||
ProfileConfigurationExportModel? exportModel = CoreJson.DeserializeObject<ProfileConfigurationExportModel>(await File.ReadAllTextAsync(result[0]));
|
ProfileConfigurationExportModel? exportModel = CoreJson.Deserialize<ProfileConfigurationExportModel>(await File.ReadAllTextAsync(result[0]));
|
||||||
if (exportModel == null)
|
if (exportModel == null)
|
||||||
{
|
{
|
||||||
await _windowService.ShowConfirmContentDialog("Import profile", "Failed to import this profile, make sure it is a valid Artemis profile.", "Confirm", 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();
|
MemoryStream archiveStream = new();
|
||||||
|
|
||||||
string configurationJson = CoreJson.SerializeObject(exportModel.ProfileConfigurationEntity);
|
string configurationJson = CoreJson.Serialize(exportModel.ProfileConfigurationEntity);
|
||||||
string profileJson = CoreJson.SerializeObject(exportModel.ProfileEntity);
|
string profileJson = CoreJson.Serialize(exportModel.ProfileEntity);
|
||||||
|
|
||||||
// Create a ZIP archive
|
// Create a ZIP archive
|
||||||
using (ZipArchive archive = new(archiveStream, ZipArchiveMode.Create, true))
|
using (ZipArchive archive = new(archiveStream, ZipArchiveMode.Create, true))
|
||||||
|
|||||||
@ -15,6 +15,7 @@ using Artemis.UI.DryIoc.Factories;
|
|||||||
using Artemis.UI.Models;
|
using Artemis.UI.Models;
|
||||||
using Artemis.UI.Screens.VisualScripting.Pins;
|
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Artemis.UI.Shared.Services.NodeEditor;
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Avalonia;
|
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();
|
List<INode> nodes = NodeViewModels.Where(vm => vm.IsSelected).Select(vm => vm.Node).Where(n => !n.IsDefaultNode && !n.IsExitNode).ToList();
|
||||||
DataObject dataObject = new();
|
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);
|
dataObject.Set(CLIPBOARD_DATA_FORMAT, copy);
|
||||||
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
await Shared.UI.Clipboard.SetDataObjectAsync(dataObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecutePasteSelected()
|
private async Task ExecutePasteSelected()
|
||||||
{
|
{
|
||||||
byte[]? bytes = (byte[]?) await Shared.UI.Clipboard.GetDataAsync(CLIPBOARD_DATA_FORMAT);
|
NodesClipboardModel? nodesClipboardModel = await Shared.UI.Clipboard.GetJsonAsync<NodesClipboardModel>(CLIPBOARD_DATA_FORMAT);
|
||||||
if (bytes == null!)
|
|
||||||
return;
|
|
||||||
|
|
||||||
NodesClipboardModel? nodesClipboardModel = CoreJson.DeserializeObject<NodesClipboardModel>(Encoding.Unicode.GetString(bytes));
|
|
||||||
if (nodesClipboardModel == null)
|
if (nodesClipboardModel == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public partial class PluginSelectionStepViewModel : SubmissionViewModel
|
|||||||
throw new ArtemisPluginException("Couldn't find a plugin.json in " + files[0]);
|
throw new ArtemisPluginException("Couldn't find a plugin.json in " + files[0]);
|
||||||
|
|
||||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
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"))
|
if (!pluginInfo.Main.EndsWith(".dll"))
|
||||||
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
|
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.WebClient.Workshop.Entities;
|
using Artemis.WebClient.Workshop.Entities;
|
||||||
using Artemis.WebClient.Workshop.Exceptions;
|
using Artemis.WebClient.Workshop.Exceptions;
|
||||||
@ -68,7 +69,7 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler
|
|||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
||||||
|
|
||||||
Release? release = CoreJson.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");
|
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using Artemis.Core;
|
using System.Net.Http.Json;
|
||||||
using Artemis.WebClient.Workshop.Entities;
|
using Artemis.WebClient.Workshop.Entities;
|
||||||
|
|
||||||
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
@ -34,7 +34,7 @@ public class PluginEntryUploadHandler : IEntryUploadHandler
|
|||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
||||||
|
|
||||||
Release? release = CoreJson.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");
|
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using Artemis.Core;
|
using System.Net.Http.Json;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.WebClient.Workshop.Entities;
|
using Artemis.WebClient.Workshop.Entities;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler
|
|||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
||||||
|
|
||||||
Release? release = CoreJson.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");
|
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user