1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Storage - Added LiteDB to SQLite migration

This commit is contained in:
Robert 2024-03-09 22:47:36 +01:00
parent 70994feb32
commit d4e4d52f84
55 changed files with 1569 additions and 19 deletions

View File

@ -434,7 +434,6 @@ internal class ProfileService : IProfileService
}
// A new GUID will be given on save
configurationEntity.FileIconId = Guid.Empty;
ProfileConfiguration profileConfiguration = new(category, containerEntity);
if (nameAffix != null)
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";

View File

@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -0,0 +1,15 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.General;
public class QueuedActionEntity
{
public QueuedActionEntity()
{
Parameters = new Dictionary<string, object>();
}
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public DateTimeOffset CreatedAt { get; set; }
public Dictionary<string, object> Parameters { get; set; }
}

View File

@ -0,0 +1,19 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.General;
public class ReleaseEntity
{
public Guid Id { get; set; }
public string Version { get; set; } = string.Empty;
public DateTimeOffset? InstalledAt { get; set; }
public Storage.Entities.General.ReleaseEntity Migrate()
{
return new Storage.Entities.General.ReleaseEntity()
{
Id = Id,
Version = Version,
InstalledAt = InstalledAt ?? DateTimeOffset.Now
};
}
}

View File

@ -0,0 +1,10 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.General;
public class ScriptConfigurationEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string ScriptingProviderId { get; set; } = string.Empty;
public string? ScriptContent { get; set; }
}

View File

@ -0,0 +1,45 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Plugins;
/// <summary>
/// Represents the configuration of a plugin, each plugin has one configuration
/// </summary>
public class PluginEntity
{
public PluginEntity()
{
Features = new List<PluginFeatureEntity>();
}
public Guid Id { get; set; }
public bool IsEnabled { get; set; }
public List<PluginFeatureEntity> Features { get; set; }
public Artemis.Storage.Entities.Plugins.PluginEntity Migrate()
{
return new Artemis.Storage.Entities.Plugins.PluginEntity()
{
Id = Id,
IsEnabled = IsEnabled,
Features = Features.Select(f => f.Migrate()).ToList()
};
}
}
/// <summary>
/// Represents the configuration of a plugin feature, each feature has one configuration
/// </summary>
public class PluginFeatureEntity
{
public string Type { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
public Artemis.Storage.Entities.Plugins.PluginFeatureEntity Migrate()
{
return new Artemis.Storage.Entities.Plugins.PluginFeatureEntity()
{
Type = Type,
IsEnabled = IsEnabled
};
}
}

View File

@ -0,0 +1,24 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Plugins;
/// <summary>
/// Represents the setting of a plugin, a plugin can have multiple settings
/// </summary>
public class PluginSettingEntity
{
public Guid Id { get; set; }
public Guid PluginGuid { get; set; }
public string Name { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public Artemis.Storage.Entities.Plugins.PluginSettingEntity Migrate()
{
return new Storage.Entities.Plugins.PluginSettingEntity
{
Id = Id,
PluginGuid = PluginGuid,
Name = Name,
Value = Value
};
}
}

View File

@ -0,0 +1,14 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract;
public abstract class RenderElementEntity
{
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; } = new();
public IConditionEntity? DisplayCondition { get; set; }
public TimelineEntity? Timeline { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints;
public class CategoryAdaptionHintEntity : IAdaptionHintEntity
{
public int Category { get; set; }
public bool LimitAmount { get; set; }
public int Skip { get; set; }
public int Amount { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints;
public class DeviceAdaptionHintEntity : IAdaptionHintEntity
{
public int DeviceType { get; set; }
public bool LimitAmount { get; set; }
public int Skip { get; set; }
public int Amount { get; set; }
}

View File

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints;
[JsonDerivedType(typeof(CategoryAdaptionHintEntity), "Category")]
[JsonDerivedType(typeof(DeviceAdaptionHintEntity), "Device")]
[JsonDerivedType(typeof(KeyboardSectionAdaptionHintEntity), "KeyboardSection")]
[JsonDerivedType(typeof(SingleLedAdaptionHintEntity), "SingleLed")]
public interface IAdaptionHintEntity;

View File

@ -0,0 +1,6 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints;
public class KeyboardSectionAdaptionHintEntity : IAdaptionHintEntity
{
public int Section { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints;
public class SingleLedAdaptionHintEntity : IAdaptionHintEntity
{
public int LedId { get; set; }
public bool LimitAmount { get; set; }
public int Skip { get; set; }
public int Amount { get; set; }
}

View File

@ -0,0 +1,3 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
public class AlwaysOnConditionEntity : IConditionEntity;

View File

@ -0,0 +1,12 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
public class EventConditionEntity : IConditionEntity
{
public int TriggerMode { get; set; }
public int OverlapMode { get; set; }
public int ToggleOffMode { get; set; }
public DataModelPathEntity? EventPath { get; set; }
public NodeScriptEntity? Script { get; set; }
}

View File

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
[JsonDerivedType(typeof(AlwaysOnConditionEntity), "AlwaysOn")]
[JsonDerivedType(typeof(EventConditionEntity), "Event")]
[JsonDerivedType(typeof(PlayOnceConditionEntity), "PlayOnce")]
[JsonDerivedType(typeof(StaticConditionEntity), "Static")]
public interface IConditionEntity;

View File

@ -0,0 +1,3 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
public class PlayOnceConditionEntity : IConditionEntity;

View File

@ -0,0 +1,10 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
public class StaticConditionEntity : IConditionEntity
{
public int PlayMode { get; set; }
public int StopMode { get; set; }
public NodeScriptEntity? Script { get; set; }
}

View File

@ -0,0 +1,9 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.DataBindings;
public class DataBindingEntity
{
public bool IsEnabled { get; set; }
public NodeScriptEntity? NodeScript { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class DataModelPathEntity
{
public string Path { get; set; } = string.Empty;
public string? DataModelId { get; set; }
public string? Type { get; set; }
}

View File

@ -0,0 +1,17 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract;
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class FolderEntity : RenderElementEntity
{
public int Order { get; set; }
public string? Name { get; set; }
public bool IsExpanded { get; set; }
public bool Suspended { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } = null!;
public Guid ProfileId { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class KeyframeEntity
{
public TimeSpan Position { get; set; }
public int Timeline { get; set; }
public string Value { get; set; } = string.Empty;
public int EasingFunction { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class LayerBrushEntity
{
public string ProviderId { get; set; } = string.Empty;
public string BrushType { get; set; } = string.Empty;
public PropertyGroupEntity? PropertyGroup { get; set; }
}

View File

@ -0,0 +1,12 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class LayerEffectEntity
{
public string ProviderId { get; set; } = string.Empty;
public string EffectType { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public bool HasBeenRenamed { get; set; }
public int Order { get; set; }
public PropertyGroupEntity? PropertyGroup { get; set; }
}

View File

@ -0,0 +1,30 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract;
using Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints;
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class LayerEntity : RenderElementEntity
{
public LayerEntity()
{
Leds = new List<LedEntity>();
AdaptionHints = new List<IAdaptionHintEntity>();
}
public int Order { get; set; }
public string? Name { get; set; }
public bool Suspended { get; set; }
public List<LedEntity> Leds { get; set; }
public List<IAdaptionHintEntity> AdaptionHints { get; set; }
public PropertyGroupEntity? GeneralPropertyGroup { get; set; }
public PropertyGroupEntity? TransformPropertyGroup { get; set; }
public LayerBrushEntity? LayerBrush { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } = null!;
public Guid ProfileId { get; set; }
}

View File

@ -0,0 +1,36 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class LedEntity
{
public string LedName { get; set; } = string.Empty;
public string DeviceIdentifier { get; set; } = string.Empty;
public int? PhysicalLayout { get; set; }
#region LedEntityEqualityComparer
private sealed class LedEntityEqualityComparer : IEqualityComparer<LedEntity>
{
public bool Equals(LedEntity? x, LedEntity? y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null))
return false;
if (ReferenceEquals(y, null))
return false;
if (x.GetType() != y.GetType())
return false;
return x.LedName == y.LedName && x.DeviceIdentifier == y.DeviceIdentifier && x.PhysicalLayout == y.PhysicalLayout;
}
public int GetHashCode(LedEntity obj)
{
return HashCode.Combine(obj.LedName, obj.DeviceIdentifier, obj.PhysicalLayout);
}
}
public static IEqualityComparer<LedEntity> LedEntityComparer { get; } = new LedEntityEqualityComparer();
#endregion
}

View File

@ -0,0 +1,29 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
public class NodeConnectionEntity
{
public NodeConnectionEntity()
{
}
public NodeConnectionEntity(NodeConnectionEntity nodeConnectionEntity)
{
SourceType = nodeConnectionEntity.SourceType;
SourceNode = nodeConnectionEntity.SourceNode;
TargetNode = nodeConnectionEntity.TargetNode;
SourcePinCollectionId = nodeConnectionEntity.SourcePinCollectionId;
SourcePinId = nodeConnectionEntity.SourcePinId;
TargetType = nodeConnectionEntity.TargetType;
TargetPinCollectionId = nodeConnectionEntity.TargetPinCollectionId;
TargetPinId = nodeConnectionEntity.TargetPinId;
}
public string SourceType { get; set; } = string.Empty;
public Guid SourceNode { get; set; }
public Guid TargetNode { get; set; }
public int SourcePinCollectionId { get; set; }
public int SourcePinId { get; set; }
public string TargetType { get; set; } = string.Empty;
public int TargetPinCollectionId { get; set; }
public int TargetPinId { get; set; }
}

View File

@ -0,0 +1,38 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
public class NodeEntity
{
public NodeEntity()
{
PinCollections = new List<NodePinCollectionEntity>();
}
public NodeEntity(NodeEntity nodeEntity)
{
Id = nodeEntity.Id;
Type = nodeEntity.Type;
ProviderId = nodeEntity.ProviderId;
Name = nodeEntity.Name;
Description = nodeEntity.Description;
IsExitNode = nodeEntity.IsExitNode;
X = nodeEntity.X;
Y = nodeEntity.Y;
Storage = nodeEntity.Storage;
PinCollections = nodeEntity.PinCollections.Select(p => new NodePinCollectionEntity(p)).ToList();
}
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public string ProviderId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsExitNode { get; set; }
public double X { get; set; }
public double Y { get; set; }
public string Storage { get; set; } = string.Empty;
public List<NodePinCollectionEntity> PinCollections { get; set; }
}

View File

@ -0,0 +1,19 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
public class NodePinCollectionEntity
{
public NodePinCollectionEntity()
{
}
public NodePinCollectionEntity(NodePinCollectionEntity nodePinCollectionEntity)
{
Id = nodePinCollectionEntity.Id;
Direction = nodePinCollectionEntity.Direction;
Amount = nodePinCollectionEntity.Amount;
}
public int Id { get; set; }
public int Direction { set; get; }
public int Amount { get; set; }
}

View File

@ -0,0 +1,16 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
public class NodeScriptEntity
{
public NodeScriptEntity()
{
Nodes = new List<NodeEntity>();
Connections = new List<NodeConnectionEntity>();
}
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<NodeEntity> Nodes { get; set; }
public List<NodeConnectionEntity> Connections { get; set; }
}

View File

@ -0,0 +1,78 @@
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using LiteDB;
using Serilog;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class ProfileCategoryEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsCollapsed { get; set; }
public bool IsSuspended { get; set; }
public int Order { get; set; }
public List<ProfileConfigurationEntity> ProfileConfigurations { get; set; } = new();
public Storage.Entities.Profile.ProfileCategoryEntity Migrate(ILogger logger, List<ProfileEntity> legacyProfiles, ILiteStorage<Guid> profileIcons)
{
Storage.Entities.Profile.ProfileCategoryEntity category = new()
{
Id = Id,
Name = Name,
IsCollapsed = IsCollapsed,
IsSuspended = IsSuspended,
Order = Order
};
foreach (ProfileConfigurationEntity legacyProfileConfiguration in ProfileConfigurations)
{
// Find the profile
ProfileEntity? legacyProfile = legacyProfiles.FirstOrDefault(p => p.Id == legacyProfileConfiguration.ProfileId);
if (legacyProfile == null)
{
logger.Information("Profile not found for profile configuration {ProfileId}", legacyProfileConfiguration.ProfileId);
continue;
}
// Clone to the new format via JSON, as both are serializable
string profileJson = CoreJson.Serialize(legacyProfile);
string configJson = CoreJson.Serialize(legacyProfileConfiguration);
Storage.Entities.Profile.ProfileEntity? profile = CoreJson.Deserialize<Storage.Entities.Profile.ProfileEntity>(profileJson);
Storage.Entities.Profile.ProfileConfigurationEntity? config = CoreJson.Deserialize<Storage.Entities.Profile.ProfileConfigurationEntity>(configJson);
if (profile == null)
{
logger.Information("Failed to deserialize profile JSON for profile configuration {ProfileId}", legacyProfileConfiguration.ProfileId);
continue;
}
if (config == null)
{
logger.Information("Failed to deserialize profile configuration JSON for profile configuration {ProfileId}", legacyProfileConfiguration.ProfileId);
continue;
}
// Add a container
ProfileContainerEntity container = new()
{
Profile = profile,
ProfileConfiguration = config,
};
// If available, download the profile icon
if (profileIcons.Exists(legacyProfileConfiguration.FileIconId))
{
using MemoryStream memoryStream = new();
profileIcons.Download(legacyProfileConfiguration.FileIconId, memoryStream);
container.Icon = memoryStream.ToArray();
}
category.ProfileConfigurations.Add(container);
}
return category;
}
}

View File

@ -0,0 +1,29 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class ProfileConfigurationEntity
{
public string Name { get; set; } = string.Empty;
public string? MaterialIcon { get; set; }
public Guid FileIconId { get; set; }
public int IconType { get; set; }
public bool IconFill { get; set; }
public int Order { get; set; }
public bool IsSuspended { get; set; }
public int ActivationBehaviour { get; set; }
public NodeScriptEntity? ActivationCondition { get; set; }
public int HotkeyMode { get; set; }
public ProfileConfigurationHotkeyEntity? EnableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity? DisableHotkey { get; set; }
public string? ModuleId { get; set; }
public Guid ProfileCategoryId { get; set; }
public Guid ProfileId { get; set; }
public bool FadeInAndOut { get; set; }
public int Version { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class ProfileConfigurationHotkeyEntity
{
public int? Key { get; set; }
public int? Modifiers { get; set; }
}

View File

@ -0,0 +1,32 @@
using Artemis.Storage.Migrator.Legacy.Entities.General;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class ProfileEntity
{
public ProfileEntity()
{
Folders = new List<FolderEntity>();
Layers = new List<LayerEntity>();
ScriptConfigurations = new List<ScriptConfigurationEntity>();
}
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsFreshImport { get; set; }
public List<FolderEntity> Folders { get; set; }
public List<LayerEntity> Layers { get; set; }
public List<ScriptConfigurationEntity> ScriptConfigurations { get; set; }
public void UpdateGuid(Guid guid)
{
Guid oldGuid = Id;
Id = guid;
FolderEntity? rootFolder = Folders.FirstOrDefault(f => f.ParentId == oldGuid);
if (rootFolder != null)
rootFolder.ParentId = Id;
}
}

View File

@ -0,0 +1,13 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile.DataBindings;
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class PropertyEntity
{
public string Identifier { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public bool KeyframesEnabled { get; set; }
public DataBindingEntity? DataBinding { get; set; }
public List<KeyframeEntity> KeyframeEntities { get; set; } = new();
}

View File

@ -0,0 +1,8 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class PropertyGroupEntity
{
public string Identifier { get; set; } = string.Empty;
public List<PropertyEntity> Properties { get; set; } = new();
public List<PropertyGroupEntity> PropertyGroups { get; set; } = new();
}

View File

@ -0,0 +1,8 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Profile;
public class TimelineEntity
{
public TimeSpan StartSegmentLength { get; set; }
public TimeSpan MainSegmentLength { get; set; }
public TimeSpan EndSegmentLength { get; set; }
}

View File

@ -0,0 +1,78 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Surface;
public class DeviceEntity
{
public DeviceEntity()
{
InputIdentifiers = new List<DeviceInputIdentifierEntity>();
InputMappings = new List<InputMappingEntity>();
Categories = new List<int>();
}
public string Id { get; set; } = string.Empty;
public string DeviceProvider { get; set; } = string.Empty;
public float X { get; set; }
public float Y { get; set; }
public float Rotation { get; set; }
public float Scale { get; set; }
public int ZIndex { get; set; }
public float RedScale { get; set; }
public float GreenScale { get; set; }
public float BlueScale { get; set; }
public bool IsEnabled { get; set; }
public int PhysicalLayout { get; set; }
public string? LogicalLayout { get; set; }
public string? LayoutType { get; set; }
public string? LayoutParameter { get; set; }
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; }
public List<int> Categories { get; set; }
public Storage.Entities.Surface.DeviceEntity Migrate()
{
// All properties match, return a copy
return new Storage.Entities.Surface.DeviceEntity()
{
Id = Id,
DeviceProvider = DeviceProvider,
X = X,
Y = Y,
Rotation = Rotation,
Scale = Scale,
ZIndex = ZIndex,
RedScale = RedScale,
GreenScale = GreenScale,
BlueScale = BlueScale,
IsEnabled = IsEnabled,
PhysicalLayout = PhysicalLayout,
LogicalLayout = LogicalLayout,
LayoutType = LayoutType,
LayoutParameter = LayoutParameter,
InputIdentifiers = InputIdentifiers.Select(i => new Storage.Entities.Surface.DeviceInputIdentifierEntity
{
InputProvider = i.InputProvider,
Identifier = i.Identifier.ToString() ?? string.Empty
}).ToList(),
InputMappings = InputMappings.Select(i => new Storage.Entities.Surface.InputMappingEntity
{
OriginalLedId = i.OriginalLedId,
MappedLedId = i.MappedLedId
}).ToList(),
Categories = Categories
};
}
}
public class InputMappingEntity
{
public int OriginalLedId { get; set; }
public int MappedLedId { get; set; }
}
public class DeviceInputIdentifierEntity
{
public string InputProvider { get; set; } = string.Empty;
public object Identifier { get; set; } = string.Empty;
}

View File

@ -0,0 +1,35 @@
namespace Artemis.Storage.Migrator.Legacy.Entities.Workshop;
public class EntryEntity
{
public Guid Id { get; set; }
public long EntryId { get; set; }
public int EntryType { get; set; }
public string Author { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public long ReleaseId { get; set; }
public string ReleaseVersion { get; set; } = string.Empty;
public DateTimeOffset InstalledAt { get; set; }
public Dictionary<string, object>? Metadata { get; set; }
public Storage.Entities.Workshop.EntryEntity Migrate()
{
// Create a copy
return new Storage.Entities.Workshop.EntryEntity()
{
Id = Id,
EntryId = EntryId,
EntryType = EntryType,
Author = Author,
Name = Name,
ReleaseId = ReleaseId,
ReleaseVersion = ReleaseVersion,
InstalledAt = InstalledAt,
Metadata = Metadata ?? new Dictionary<string, object>()
};
}
}

View File

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

View File

@ -0,0 +1,9 @@
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations;
public interface IStorageMigration
{
int UserVersion { get; }
void Apply(LiteRepository repository);
}

View File

@ -0,0 +1,17 @@
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0020AvaloniaReset : IStorageMigration
{
public int UserVersion => 20;
public void Apply(LiteRepository repository)
{
repository.Database.Commit();
List<string> collectionNames = repository.Database.GetCollectionNames().ToList();
foreach (string collectionName in collectionNames)
repository.Database.DropCollection(collectionName);
}
}

View File

@ -0,0 +1,87 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile;
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0021GradientNodes : IStorageMigration
{
private void MigrateDataBinding(PropertyEntity property)
{
NodeScriptEntity? script = property.DataBinding?.NodeScript;
NodeEntity? exitNode = script?.Nodes.FirstOrDefault(s => s.IsExitNode);
if (script == null || exitNode == null)
return;
// Create a new node at the same position of the exit node
NodeEntity gradientNode = new()
{
Id = Guid.NewGuid(),
Type = "ColorGradientNode",
ProviderId = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78",
Name = "Color Gradient",
Description = "Outputs a color gradient with the given colors",
X = exitNode.X,
Y = exitNode.Y,
Storage = property.Value // Copy the value of the property into the node storage
};
script.Nodes.Add(gradientNode);
// Move all connections of the exit node to the new node
foreach (NodeConnectionEntity connection in script.Connections)
{
if (connection.SourceNode == exitNode.Id)
{
connection.SourceNode = gradientNode.Id;
connection.SourcePinId++;
}
}
// Connect the data binding node to the source node
script.Connections.Add(new NodeConnectionEntity
{
SourceType = "ColorGradient",
SourceNode = exitNode.Id,
SourcePinCollectionId = -1,
SourcePinId = 0,
TargetType = "ColorGradient",
TargetNode = gradientNode.Id,
TargetPinCollectionId = -1,
TargetPinId = 0
});
// Move the exit node to the right
exitNode.X += 300;
exitNode.Y += 30;
}
private void MigrateDataBinding(PropertyGroupEntity? propertyGroup)
{
if (propertyGroup == null)
return;
foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups)
MigrateDataBinding(propertyGroupPropertyGroup);
foreach (PropertyEntity property in propertyGroup.Properties)
{
if (property.Value.StartsWith("[{\"Color\":\"") && property.DataBinding?.NodeScript != null && property.DataBinding.IsEnabled)
MigrateDataBinding(property);
}
}
public int UserVersion => 21;
public void Apply(LiteRepository repository)
{
// Find all color gradient data bindings, there's no really good way to do this so infer it from the value
List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList();
foreach (ProfileEntity profileEntity in profiles)
{
foreach (LayerEntity layer in profileEntity.Layers.Where(le => le.LayerBrush != null))
MigrateDataBinding(layer.LayerBrush?.PropertyGroup);
repository.Update(profileEntity);
}
}
}

View File

@ -0,0 +1,84 @@
using Artemis.Storage.Migrator.Legacy.Entities.Profile;
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions;
using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes;
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0022TransitionNodes : IStorageMigration
{
private void MigrateNodeScript(NodeScriptEntity? nodeScript)
{
if (nodeScript == null)
return;
foreach (NodeEntity node in nodeScript.Nodes)
{
if (node.Type == "NumericEasingNode")
node.Type = "NumericTransitionNode";
else if (node.Type == "ColorGradientEasingNode")
node.Type = "ColorGradientTransitionNode";
else if (node.Type == "SKColorEasingNode")
node.Type = "SKColorTransitionNode";
else if (node.Type == "EasingTypeNode")
node.Type = "EasingFunctionNode";
}
}
private void MigratePropertyGroup(PropertyGroupEntity? propertyGroup)
{
if (propertyGroup == null)
return;
foreach (PropertyGroupEntity childPropertyGroup in propertyGroup.PropertyGroups)
MigratePropertyGroup(childPropertyGroup);
foreach (PropertyEntity property in propertyGroup.Properties)
MigrateNodeScript(property.DataBinding?.NodeScript);
}
private void MigrateDisplayCondition(IConditionEntity? conditionEntity)
{
if (conditionEntity is EventConditionEntity eventConditionEntity)
MigrateNodeScript(eventConditionEntity.Script);
else if (conditionEntity is StaticConditionEntity staticConditionEntity)
MigrateNodeScript(staticConditionEntity.Script);
}
public int UserVersion => 22;
public void Apply(LiteRepository repository)
{
// Migrate profile configuration display conditions
List<ProfileCategoryEntity> categories = repository.Query<ProfileCategoryEntity>().ToList();
foreach (ProfileCategoryEntity profileCategoryEntity in categories)
{
foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations)
MigrateNodeScript(profileConfigurationEntity.ActivationCondition);
repository.Update(profileCategoryEntity);
}
// Migrate profile display conditions and data bindings
List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList();
foreach (ProfileEntity profileEntity in profiles)
{
foreach (LayerEntity layer in profileEntity.Layers)
{
MigratePropertyGroup(layer.LayerBrush?.PropertyGroup);
MigratePropertyGroup(layer.GeneralPropertyGroup);
MigratePropertyGroup(layer.TransformPropertyGroup);
foreach (LayerEffectEntity layerEffectEntity in layer.LayerEffects)
MigratePropertyGroup(layerEffectEntity.PropertyGroup);
MigrateDisplayCondition(layer.DisplayCondition);
}
foreach (FolderEntity folder in profileEntity.Folders)
{
foreach (LayerEffectEntity folderLayerEffect in folder.LayerEffects)
MigratePropertyGroup(folderLayerEffect.PropertyGroup);
MigrateDisplayCondition(folder.DisplayCondition);
}
repository.Update(profileEntity);
}
}
}

View File

@ -0,0 +1,33 @@
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0023LayoutProviders : IStorageMigration
{
public int UserVersion => 23;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> deviceEntities = repository.Database.GetCollection("DeviceEntity");
List<BsonDocument> toUpdate = new();
foreach (BsonDocument bsonDocument in deviceEntities.FindAll())
{
if (bsonDocument.TryGetValue("CustomLayoutPath", out BsonValue customLayoutPath) && customLayoutPath.IsString && !string.IsNullOrEmpty(customLayoutPath.AsString))
{
bsonDocument.Add("LayoutType", new BsonValue("CustomPath"));
bsonDocument.Add("LayoutParameter", new BsonValue(customLayoutPath.AsString));
}
else if (bsonDocument.TryGetValue("DisableDefaultLayout", out BsonValue disableDefaultLayout) && disableDefaultLayout.AsBoolean)
bsonDocument.Add("LayoutType", new BsonValue("None"));
else
bsonDocument.Add("LayoutType", new BsonValue("Default"));
bsonDocument.Remove("CustomLayoutPath");
bsonDocument.Remove("DisableDefaultLayout");
toUpdate.Add(bsonDocument);
}
deviceEntities.Update(toUpdate);
}
}

View File

@ -0,0 +1,105 @@
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0024NodeProviders : IStorageMigration
{
public int UserVersion => 24;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity");
List<BsonDocument> categoriesToUpdate = new();
foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll())
{
BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray;
if (profiles != null)
{
foreach (BsonValue profile in profiles)
profile["Version"] = 1;
categoriesToUpdate.Add(profileCategoryBson);
}
}
categoryCollection.Update(categoriesToUpdate);
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
List<BsonDocument> profilesToUpdate = new();
foreach (BsonDocument profileBson in collection.FindAll())
{
BsonArray? folders = profileBson["Folders"]?.AsArray;
BsonArray? layers = profileBson["Layers"]?.AsArray;
if (folders != null)
{
foreach (BsonValue folder in folders)
MigrateProfileElement(folder.AsDocument);
}
if (layers != null)
{
foreach (BsonValue layer in layers)
{
MigrateProfileElement(layer.AsDocument);
MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument);
}
}
profilesToUpdate.Add(profileBson);
}
collection.Update(profilesToUpdate);
}
private void MigrateProfileElement(BsonDocument profileElement)
{
BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray;
if (layerEffects != null)
{
foreach (BsonValue layerEffect in layerEffects)
MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument);
}
BsonValue? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument);
}
private void MigratePropertyGroup(BsonDocument? propertyGroup)
{
if (propertyGroup == null || propertyGroup.Keys.Count == 0)
return;
BsonArray? properties = propertyGroup["Properties"]?.AsArray;
BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray;
if (properties != null)
{
foreach (BsonValue property in properties)
MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument);
}
if (propertyGroups != null)
{
foreach (BsonValue childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup.AsDocument);
}
}
private void MigrateNodeScript(BsonDocument? nodeScript)
{
if (nodeScript == null || nodeScript.Keys.Count == 0)
return;
BsonArray? nodes = nodeScript["Nodes"]?.AsArray;
if (nodes == null)
return;
foreach (BsonValue node in nodes)
{
node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -0,0 +1,45 @@
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0025NodeProvidersProfileConfig : IStorageMigration
{
public int UserVersion => 25;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity");
List<BsonDocument> toUpdate = new();
foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll())
{
BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray;
if (profiles != null)
{
foreach (BsonValue profile in profiles)
{
profile["Version"] = 2;
MigrateNodeScript(profile["ActivationCondition"]?.AsDocument);
}
toUpdate.Add(profileCategoryBson);
}
}
categoryCollection.Update(toUpdate);
}
private void MigrateNodeScript(BsonDocument? nodeScript)
{
if (nodeScript == null || nodeScript.Keys.Count == 0)
return;
BsonArray? nodes = nodeScript["Nodes"]?.AsArray;
if (nodes == null)
return;
foreach (BsonValue node in nodes)
{
node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -0,0 +1,227 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using LiteDB;
using Serilog;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0026NodeStorage : IStorageMigration
{
private readonly ILogger _logger;
public M0026NodeStorage(ILogger logger)
{
_logger = logger;
}
public int UserVersion => 26;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity");
List<BsonDocument> toUpdate = new();
foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll())
{
BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray;
if (profiles != null)
{
foreach (BsonValue profile in profiles)
{
profile["Version"] = 4;
MigrateNodeScript(profile["ActivationCondition"]?.AsDocument);
}
toUpdate.Add(profileCategoryBson);
}
}
categoryCollection.Update(toUpdate);
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
List<BsonDocument> profilesToUpdate = new();
foreach (BsonDocument profileBson in collection.FindAll())
{
BsonArray? folders = profileBson["Folders"]?.AsArray;
BsonArray? layers = profileBson["Layers"]?.AsArray;
if (folders != null)
{
foreach (BsonValue folder in folders)
MigrateProfileElement(folder.AsDocument);
}
if (layers != null)
{
foreach (BsonValue layer in layers)
{
MigrateProfileElement(layer.AsDocument);
MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument);
}
}
profilesToUpdate.Add(profileBson);
}
collection.Update(profilesToUpdate);
}
private void MigrateProfileElement(BsonDocument profileElement)
{
BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray;
if (layerEffects != null)
{
foreach (BsonValue layerEffect in layerEffects)
MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument);
}
BsonValue? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument);
}
private void MigratePropertyGroup(BsonDocument? propertyGroup)
{
if (propertyGroup == null || propertyGroup.Keys.Count == 0)
return;
BsonArray? properties = propertyGroup["Properties"]?.AsArray;
BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray;
if (properties != null)
{
foreach (BsonValue property in properties)
MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument);
}
if (propertyGroups != null)
{
foreach (BsonValue childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup.AsDocument);
}
}
private void MigrateNodeScript(BsonDocument? nodeScript)
{
if (nodeScript == null || nodeScript.Keys.Count == 0)
return;
BsonArray? nodes = nodeScript["Nodes"]?.AsArray;
if (nodes == null)
return;
foreach (BsonValue node in nodes)
{
// Migrate the storage of the node
node["Storage"] = MigrateNodeStorageJson(node.AsDocument["Storage"]?.AsString, _logger);
}
}
private static string? MigrateNodeStorageJson(string? json, ILogger logger)
{
if (string.IsNullOrEmpty(json))
return json;
try
{
JsonDocument jsonDocument = JsonDocument.Parse(json);
if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object)
return json;
JsonObject? jsonObject = JsonObject.Create(jsonDocument.RootElement);
if (jsonObject == null)
return json;
if (jsonObject["$type"] != null && jsonObject["$values"] != null)
{
JsonArray? values = jsonObject["$values"]?.AsArray();
if (values != null)
{
foreach (JsonNode? jsonNode in values.ToList())
{
if (jsonNode is JsonObject childObject)
ConvertToSystemTextJson(childObject);
}
return values.ToJsonString();
}
}
else
{
ConvertToSystemTextJson(jsonObject);
}
return jsonObject.ToJsonString();
}
catch (Exception e)
{
logger.Error(e, "Failed to migrate node storage JSON");
return json;
}
}
private static 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 static 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

@ -0,0 +1,45 @@
using LiteDB;
namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage;
public class M0027Namespace : IStorageMigration
{
public int UserVersion => 27;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
List<BsonDocument> profilesToUpdate = new();
foreach (BsonDocument profileBson in collection.FindAll())
{
MigrateDocument(profileBson);
profilesToUpdate.Add(profileBson);
}
collection.Update(profilesToUpdate);
}
private void MigrateDocument(BsonDocument document)
{
foreach ((string? key, BsonValue? value) in document)
{
if (key == "_type")
{
document[key] = document[key].AsString
.Replace("Artemis.Storage.Entities.Profile", "Artemis.Storage.Migrator.Legacy.Entities.Profile")
.Replace(", Artemis.Storage", ", Artemis.Storage.Migrator");
}
else if (value.IsDocument)
MigrateDocument(value.AsDocument);
else if (value.IsArray)
{
foreach (BsonValue bsonValue in value.AsArray)
{
if (bsonValue.IsDocument)
MigrateDocument(bsonValue.AsDocument);
}
}
}
}
}

View File

@ -0,0 +1,34 @@
using Artemis.Storage.Migrator.Legacy.Migrations;
using LiteDB;
using Serilog;
namespace Artemis.Storage.Migrator.Legacy;
public static class StorageMigrationService
{
public static void ApplyPendingMigrations(ILogger logger, LiteRepository repository, IList<IStorageMigration> migrations)
{
foreach (IStorageMigration storageMigration in migrations.OrderBy(m => m.UserVersion))
{
if (repository.Database.UserVersion >= storageMigration.UserVersion)
continue;
logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}",
storageMigration.GetType().Name, repository.Database.UserVersion, storageMigration.UserVersion);
repository.Database.BeginTrans();
try
{
storageMigration.Apply(repository);
}
catch (Exception)
{
repository.Database.Rollback();
throw;
}
repository.Database.Commit();
repository.Database.UserVersion = storageMigration.UserVersion;
}
}
}

View File

@ -1,6 +1,16 @@
using Artemis.Core.DryIoc;
using Artemis.Core;
using Artemis.Core.DryIoc;
using Artemis.Storage.Migrator.Legacy;
using Artemis.Storage.Migrator.Legacy.Entities.General;
using Artemis.Storage.Migrator.Legacy.Entities.Plugins;
using Artemis.Storage.Migrator.Legacy.Entities.Profile;
using Artemis.Storage.Migrator.Legacy.Entities.Surface;
using Artemis.Storage.Migrator.Legacy.Entities.Workshop;
using Artemis.Storage.Migrator.Legacy.Migrations.Storage;
using DryIoc;
using LiteDB;
using Microsoft.EntityFrameworkCore;
using Serilog;
namespace Artemis.Storage.Migrator;
@ -8,12 +18,125 @@ class Program
{
static void Main(string[] args)
{
Container container = new Container(rules => rules
using Container container = new(rules => rules
.WithMicrosoftDependencyInjectionRules()
.WithConcreteTypeDynamicRegistrations()
.WithoutThrowOnRegisteringDisposableTransient());
container.RegisterCore();
container.Resolve<ArtemisDbContext>().Database.EnsureCreated();
ILogger logger = container.Resolve<ILogger>();
ArtemisDbContext dbContext = container.Resolve<ArtemisDbContext>();
logger.Information("Applying pending migrations...");
dbContext.Database.Migrate();
logger.Information("Pending migrations applied");
if (!File.Exists(Path.Combine(Constants.DataFolder, "database.db")))
{
logger.Information("No legacy database found, nothing to migrate");
return;
}
logger.Information("Migrating legacy database...");
try
{
MigrateLegacyDatabase(logger, dbContext);
// After a successful migration, keep the legacy database around for a while
File.Move(Path.Combine(Constants.DataFolder, "database.db"), Path.Combine(Constants.DataFolder, "legacy.db"));
}
catch (Exception e)
{
logger.Error(e, "Failed to migrate legacy database");
throw;
}
finally
{
File.Delete(Path.Combine(Constants.DataFolder, "temp.db"));
}
logger.Information("Legacy database migrated");
}
private static void MigrateLegacyDatabase(ILogger logger, ArtemisDbContext dbContext)
{
// Copy the database before using it, we're going to make some modifications to it and we don't want to mess up the original
string databasePath = Path.Combine(Constants.DataFolder, "database.db");
string tempPath = Path.Combine(Constants.DataFolder, "temp.db");
File.Copy(databasePath, tempPath, true);
using LiteRepository repository = new($"FileName={tempPath}");
// Apply pending LiteDB migrations, this includes a migration that transforms namespaces to Artemis.Storage.Migrator
StorageMigrationService.ApplyPendingMigrations(
logger,
repository,
[
new M0020AvaloniaReset(),
new M0021GradientNodes(),
new M0022TransitionNodes(),
new M0023LayoutProviders(),
new M0024NodeProviders(),
new M0025NodeProvidersProfileConfig(),
new M0026NodeStorage(logger),
new M0027Namespace(),
]
);
// Devices
if (!dbContext.Devices.Any())
{
logger.Information("Migrating devices");
List<DeviceEntity> legacyDevices = repository.Query<DeviceEntity>().Include(s => s.InputIdentifiers).ToList();
dbContext.Devices.AddRange(legacyDevices.Select(l => l.Migrate()));
dbContext.SaveChanges();
}
// Entries
if (!dbContext.Entries.Any())
{
logger.Information("Migrating entries");
List<EntryEntity> legacyEntries = repository.Query<EntryEntity>().ToList();
dbContext.Entries.AddRange(legacyEntries.Select(l => l.Migrate()));
dbContext.SaveChanges();
}
// Plugins
if (!dbContext.Plugins.Any())
{
logger.Information("Migrating plugins");
List<PluginEntity> legacyPlugins = repository.Query<PluginEntity>().ToList();
dbContext.Plugins.AddRange(legacyPlugins.Select(l => l.Migrate()));
dbContext.SaveChanges();
}
// PluginSettings
if (!dbContext.PluginSettings.Any())
{
logger.Information("Migrating plugin settings");
List<PluginSettingEntity> legacyPluginSettings = repository.Query<PluginSettingEntity>().ToList();
dbContext.PluginSettings.AddRange(legacyPluginSettings.Select(l => l.Migrate()));
dbContext.SaveChanges();
}
// ProfileCategories
if (!dbContext.ProfileCategories.Any())
{
logger.Information("Migrating profile categories");
List<ProfileCategoryEntity> legacyProfileCategories = repository.Query<ProfileCategoryEntity>().ToList();
ILiteStorage<Guid> profileIcons = repository.Database.GetStorage<Guid>("profileIcons");
List<ProfileEntity> legacyProfiles = repository.Query<ProfileEntity>().ToList();
dbContext.ProfileCategories.AddRange(legacyProfileCategories.Select(l => l.Migrate(logger, legacyProfiles, profileIcons)));
dbContext.SaveChanges();
}
// Releases
if (!dbContext.Releases.Any())
{
logger.Information("Migrating releases");
List<ReleaseEntity> legacyReleases = repository.Query<ReleaseEntity>().ToList();
dbContext.Releases.AddRange(legacyReleases.Select(l => l.Migrate()));
dbContext.SaveChanges();
}
}
}

View File

@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LiteDB" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Serilog" />
<PackageReference Include="System.Text.Json" />

View File

@ -1,6 +1,5 @@
using System;
using Artemis.Storage.Entities.Profile.Nodes;
using Serilog.Core;
namespace Artemis.Storage.Entities.Profile;
@ -8,7 +7,6 @@ public class ProfileConfigurationEntity
{
public string Name { get; set; } = string.Empty;
public string? MaterialIcon { get; set; }
public Guid FileIconId { get; set; }
public int IconType { get; set; }
public bool IconFill { get; set; }
public int Order { get; set; }

View File

@ -59,20 +59,27 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Stream stream)
{
_stream = stream;
if (!configurationIcon.Fill)
try
{
Content = new Image {Source = new Bitmap(stream)};
return;
}
_stream = stream;
if (!configurationIcon.Fill)
{
Content = new Image {Source = new Bitmap(stream)};
return;
}
Content = new Border
Content = new Border
{
Background = TextElement.GetForeground(this),
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch,
OpacityMask = new ImageBrush(new Bitmap(stream))
};
}
catch (Exception)
{
Background = TextElement.GetForeground(this),
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch,
OpacityMask = new ImageBrush(new Bitmap(stream))
};
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
}
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)

View File

@ -25,7 +25,6 @@
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IdentityModel" Version="6.2.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="LiteDB" Version="5.0.17" />
<PackageVersion Include="Markdown.Avalonia.Tight" Version="11.0.2" />
<PackageVersion Include="Material.Icons.Avalonia" Version="2.1.0" />
<PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" />