diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ProfileMigratorExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ProfileMigratorExtensions.cs new file mode 100644 index 00000000..91a2f34f --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Extensions/ProfileMigratorExtensions.cs @@ -0,0 +1,59 @@ +using SPTarkov.Server.Core.Migration; + +namespace SPTarkov.Server.Core.Extensions; + +public static class ProfileMigratorExtensions +{ + public static IEnumerable Sort(this IEnumerable profileMigrations) + { + var sortedMigrations = new List(); + var visitedMigrations = new Dictionary(); + var migrationDict = profileMigrations.ToDictionary(m => m.GetType()); + + foreach (var migration in profileMigrations) + { + VisitMigrationForSort(migration, migrationDict, visitedMigrations, sortedMigrations); + } + + return sortedMigrations; + } + + internal static void VisitMigrationForSort( + IProfileMigration migration, + Dictionary migrationTypeDictionary, + Dictionary visitedTypeDictionary, + List sortedMigrations + ) + { + var migrationType = migration.GetType(); + + if (visitedTypeDictionary.TryGetValue(migrationType, out var isVisited)) + { + if (isVisited) + { + return; + } + + // Big error, two migrations should never depend on one another + throw new InvalidOperationException($"Cycle detected in migration prerequisites involving: {migrationType.Name}"); + } + + // Mark the current migration type for visiting + visitedTypeDictionary[migrationType] = false; + + foreach (var prerequisiteType in migration.PrerequisiteMigrations) + { + if (!migrationTypeDictionary.TryGetValue(prerequisiteType, out var prereqMigration)) + { + continue; + } + + // Visit the next prerequisite + VisitMigrationForSort(prereqMigration, migrationTypeDictionary, visitedTypeDictionary, sortedMigrations); + } + + // Done visiting, mark it as fully visited and add it to the sorted migrations + visitedTypeDictionary[migrationType] = true; + sortedMigrations.Add(migration); + } +} diff --git a/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs b/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs index 9f154dce..aec18c8f 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs @@ -5,11 +5,11 @@ namespace SPTarkov.Server.Core.Migration; public abstract class AbstractProfileMigration : IProfileMigration { + public virtual string MigrationName { get; } + public virtual IEnumerable PrerequisiteMigrations { get; } + public abstract string FromVersion { get; } public abstract string ToVersion { get; } - public abstract string MigrationName { get; } - - public abstract IEnumerable PrerequisiteMigrations { get; } public abstract bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations); diff --git a/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs b/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs index 899a99ca..f70c4538 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs @@ -5,6 +5,16 @@ namespace SPTarkov.Server.Core.Migration; public interface IProfileMigration { + /// + /// The name of the migration + /// + public abstract string MigrationName { get; } + + /// + /// An IEnumerable of migrations that need to come before the current one + /// + public abstract IEnumerable PrerequisiteMigrations { get; } + /// /// Allows for adding checks if the profile in question can migrate /// diff --git a/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs b/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs index e174d27e..623b1497 100644 --- a/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Migration; using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Utils; @@ -15,18 +16,10 @@ public class ProfileMigratorService( ISptLogger logger ) { - private IEnumerable _sortedMigrations = []; + private readonly IEnumerable _sortedMigrations = profileMigrations.Sort(); public SptProfile HandlePendingMigrations(JsonObject profile) { - // On the initial run, begin sorting our migrations - // This will sort it so that any non-prerequisite migrations go first - // And then all the prerequisite ones. - if (!_sortedMigrations.Any()) - { - _sortedMigrations = SortMigrations(); - } - var profileId = profile["info"]?["id"]?.GetValue(); // Profile is due for a wipe or a reset, do not continue here. @@ -40,7 +33,7 @@ public class ProfileMigratorService( ?? throw new InvalidOperationException($"Could not deserialize the profile: {profileId}"); } - var ranMigrations = new List(); + var ranMigrations = new List(); foreach (var profileMigration in _sortedMigrations) { @@ -98,57 +91,4 @@ public class ProfileMigratorService( return sptReadyProfile; } - - protected IEnumerable SortMigrations() - { - var sortedMigrations = new List(); - var visitedMigrations = new Dictionary(); - var migrationDict = profileMigrations.Cast().ToDictionary(m => m.GetType()); - - foreach (var migration in profileMigrations.Cast()) - { - VisitMigrationForSort(migration, migrationDict, visitedMigrations, sortedMigrations); - } - - return sortedMigrations; - } - - protected void VisitMigrationForSort( - AbstractProfileMigration migration, - Dictionary migrationTypeDictionary, - Dictionary visitedTypeDictionary, - List sortedMigrations - ) - { - var migrationType = migration.GetType(); - - if (visitedTypeDictionary.TryGetValue(migrationType, out var isVisited)) - { - if (isVisited) - { - return; - } - - // Big error, two migrations should never depend on one another - throw new InvalidOperationException($"Cycle detected in migration prerequisites involving: {migrationType.Name}"); - } - - // Mark the current migration type for visiting - visitedTypeDictionary[migrationType] = false; - - foreach (var prerequisiteType in migration.PrerequisiteMigrations) - { - if (!migrationTypeDictionary.TryGetValue(prerequisiteType, out var prereqMigration)) - { - continue; - } - - // Visit the next prerequisite - VisitMigrationForSort(prereqMigration, migrationTypeDictionary, visitedTypeDictionary, sortedMigrations); - } - - // Done visiting, mark it as fully visited and add it to the sorted migrations - visitedTypeDictionary[migrationType] = true; - sortedMigrations.Add(migration); - } }