Core - Fix profile service, storage and device bugs found by the tests (#912)

* Core - Fix default headset layouts and the z-index of devices placed by the auto-arrange fallback

- Headsets with 2 or 4 LEDs get their default layout, the files are named "... LEDs headset.xml"
- Devices placed by the auto-arrange fallback get the z-index of their type

* Core - Fall back to the default value passed to GetSetting when a stored setting can't be read

* Core - Fix storing, cloning and migrating profiles

- New profiles are stored at the current migration version, so they're no longer migrated on every start and can be duplicated right away
- A category created at the top is listed first right away, so its profiles render on top
- CloneProfileConfiguration makes a copy of the stored data, saving a clone stores its profile through the original
- Importing a profile raises ProfileAdded
- A profile configuration that isn't valid JSON is logged and skipped instead of stopping all migrations
- A stored profile without a version is migrated from version 0, like an imported one
- ProfileCategoryRepository.IsUnique returns true when the name is free
- M0001 and M0004 leave a missing node type or storage out instead of writing null
- M0003 also unwraps a list that is an item of another list
This commit is contained in:
RobertBeekman authored and GitHub committed 2026-10-24 16:19:23 +02:00
1 parent 295038f338
commit 83cf6da830
20 files changed
+137 -114

No files matched your search

@@ -54,6 +54,8 @@ internal class SurfaceArrangementType
) {SurfaceArrangement = SurfaceArrangement};
fallback.Apply(devicesToArrange, devices);
AppliedConfiguration = fallback;
foreach (ArtemisDevice artemisDevice in devicesToArrange)
artemisDevice.ZIndex = ZIndex;
}
public Point GetEdge(HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition)
@@ -161,7 +161,16 @@ internal class ProfileService : IProfileService
/// <inheritdoc />
public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration)
{
return new ProfileConfiguration(profileConfiguration.Category, profileConfiguration.Entity);
ProfileContainerEntity original = profileConfiguration.Entity;
ProfileContainerEntity copy = new()
{
Id = original.Id,
Icon = original.Icon.ToArray(),
ProfileCategory = original.ProfileCategory,
ProfileConfiguration = CopyEntity(original.ProfileConfiguration),
Profile = CopyEntity(original.Profile)
};
return new ProfileConfiguration(profileConfiguration.Category, copy);
}
/// <inheritdoc />
@@ -234,7 +243,7 @@ internal class ProfileService : IProfileService
}
_profileCategoryRepository.Add(profileCategory.Entity);
ProfileCategories = new ReadOnlyCollection<ProfileCategory>([..ProfileCategories, profileCategory]);
ProfileCategories = new ReadOnlyCollection<ProfileCategory>(ProfileCategories.Append(profileCategory).OrderBy(c => c.Order).ToList());
OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory));
return profileCategory;
@@ -259,6 +268,8 @@ internal class ProfileService : IProfileService
category.AddProfileConfiguration(configuration, category.ProfileConfigurations.FirstOrDefault());
SaveProfileCategory(category);
// The profile is in the current format, saving it stores the current migration version with it
_profileRepository.Save(configuration.Entity);
OnProfileAdded(new ProfileConfigurationEventArgs(configuration));
return configuration;
@@ -303,7 +314,18 @@ internal class ProfileService : IProfileService
profile.IsFreshImport = false;
profile.ProfileEntity.IsFreshImport = false;
_profileRepository.Save(profile.Configuration.Entity);
// A copy made by CloneProfileConfiguration has its own stored data, save it into that of the original
ProfileContainerEntity entity = profile.Configuration.Entity;
ProfileConfiguration? original = ProfileCategories
.SelectMany(c => c.ProfileConfigurations)
.FirstOrDefault(c => c.Entity != entity && c.Entity.Id == entity.Id);
if (original != null)
{
original.Entity.Profile = CopyEntity(entity.Profile);
entity = original.Entity;
}
_profileRepository.Save(entity);
// If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance
// A bit dodge but it ensures local instances always represent the latest stored version
@@ -429,6 +451,7 @@ internal class ProfileService : IProfileService
profileConfiguration.LoadModules(modules);
SaveProfileCategory(category);
OnProfileAdded(new ProfileConfigurationEventArgs(profileConfiguration));
return profileConfiguration;
}
@@ -448,6 +471,11 @@ internal class ProfileService : IProfileService
SaveProfileCategory(profile.Configuration.Category);
}
private static T CopyEntity<T>(T entity)
{
return CoreJson.Deserialize<T>(CoreJson.Serialize(entity))!;
}
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{
_pendingKeyboardEvents.Add(e);