From 6f66ecd77be4d6e5fc9b9a03308b2600ebf92c31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Nov 2021 15:41:18 +0900 Subject: [PATCH] Move migrations to own file and add user skin choice config migration --- osu.Game/Database/EFToRealmMigrator.cs | 147 +++++++++++++++++++++++ osu.Game/Database/RealmContextFactory.cs | 107 ----------------- osu.Game/OsuGameBase.cs | 2 + 3 files changed, 149 insertions(+), 107 deletions(-) create mode 100644 osu.Game/Database/EFToRealmMigrator.cs diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs new file mode 100644 index 0000000000..670eb95ad6 --- /dev/null +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -0,0 +1,147 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Game.Configuration; +using osu.Game.Models; +using osu.Game.Skinning; + +#nullable enable + +namespace osu.Game.Database +{ + internal class EFToRealmMigrator + { + private readonly DatabaseContextFactory efContextFactory; + private readonly RealmContextFactory realmContextFactory; + private readonly OsuConfigManager config; + + public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config) + { + this.efContextFactory = efContextFactory; + this.realmContextFactory = realmContextFactory; + this.config = config; + } + + public void Run() + { + using (var db = efContextFactory.GetForWrite()) + { + migrateSettings(db); + migrateSkins(db); + } + } + + private void migrateSkins(DatabaseWriteUsage db) + { + var userSkinChoice = config.GetBindable(OsuSetting.Skin); + int.TryParse(userSkinChoice.Value, out int userSkinInt); + + switch (userSkinInt) + { + case EFSkinInfo.DEFAULT_SKIN: + userSkinChoice.Value = SkinInfo.DEFAULT_SKIN.ToString(); + break; + + case EFSkinInfo.CLASSIC_SKIN: + userSkinChoice.Value = SkinInfo.CLASSIC_SKIN.ToString(); + break; + } + + // migrate ruleset settings. can be removed 20220530. + var existingSkins = db.Context.SkinInfo + .Include(s => s.Files) + .ThenInclude(f => f.FileInfo) + .ToList(); + + // previous entries in EF are removed post migration. + if (!existingSkins.Any()) + return; + + using (var realm = realmContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + // only migrate data if the realm database is empty. + if (!realm.All().Any(s => !s.Protected)) + { + foreach (var skin in existingSkins) + { + var realmSkin = new SkinInfo + { + Name = skin.Name, + Creator = skin.Creator, + Hash = skin.Hash, + Protected = false, + InstantiationInfo = skin.InstantiationInfo, + }; + + foreach (var file in skin.Files) + { + var realmFile = realm.Find(file.FileInfo.Hash); + + if (realmFile == null) + realm.Add(realmFile = new RealmFile { Hash = file.FileInfo.Hash }); + + realmSkin.Files.Add(new RealmNamedFileUsage(realmFile, file.Filename)); + } + + realm.Add(realmSkin); + + if (skin.ID == userSkinInt) + userSkinChoice.Value = realmSkin.ID.ToString(); + } + } + + db.Context.RemoveRange(existingSkins); + // Intentionally don't clean up the files, so they don't get purged by EF. + + transaction.Commit(); + } + } + + private void migrateSettings(DatabaseWriteUsage db) + { + // migrate ruleset settings. can be removed 20220315. + var existingSettings = db.Context.DatabasedSetting; + + // previous entries in EF are removed post migration. + if (!existingSettings.Any()) + return; + + using (var realm = realmContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + // only migrate data if the realm database is empty. + if (!realm.All().Any()) + { + foreach (var dkb in existingSettings) + { + if (dkb.RulesetID == null) + continue; + + string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); + + if (string.IsNullOrEmpty(shortName)) + continue; + + realm.Add(new RealmRulesetSetting + { + Key = dkb.Key, + Value = dkb.StringValue, + RulesetName = shortName, + Variant = dkb.Variant ?? 0, + }); + } + } + + db.Context.RemoveRange(existingSettings); + + transaction.Commit(); + } + } + + private string? getRulesetShortNameFromLegacyID(long rulesetId) => + efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; + } +} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 290b42311c..c6acd3097c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using System.Reflection; using System.Threading; -using Microsoft.EntityFrameworkCore; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Input.Bindings; @@ -103,10 +102,6 @@ namespace osu.Game.Database // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. cleanupPendingDeletions(); - - // Data migration is handled separately from schema migrations. - // This is required as the user may be initialising realm for the first time ever, which would result in no schema migrations running. - migrateDataFromEF(); } private void cleanupPendingDeletions() @@ -200,108 +195,6 @@ namespace osu.Game.Database }; } - private void migrateDataFromEF() - { - if (efContextFactory == null) - return; - - using (var db = efContextFactory.GetForWrite()) - { - migrateSettings(db); - migrateSkins(db); - } - - void migrateSkins(DatabaseWriteUsage db) - { - // migrate ruleset settings. can be removed 20220530. - var existingSkins = db.Context.SkinInfo - .Include(s => s.Files) - .ThenInclude(f => f.FileInfo); - - // previous entries in EF are removed post migration. - if (!existingSkins.Any()) - return; - - using (var realm = CreateContext()) - using (var transaction = realm.BeginWrite()) - { - // only migrate data if the realm database is empty. - if (!realm.All().Any(s => !s.Protected)) - { - foreach (var skin in existingSkins) - { - var realmSkin = new SkinInfo - { - Name = skin.Name, - Creator = skin.Creator, - Hash = skin.Hash, - Protected = false, - InstantiationInfo = skin.InstantiationInfo, - }; - - foreach (var file in skin.Files) - { - var realmFile = realm.Find(file.FileInfo.Hash); - - if (realmFile == null) - realm.Add(realmFile = new RealmFile { Hash = file.FileInfo.Hash }); - - realmSkin.Files.Add(new RealmNamedFileUsage(realmFile, file.Filename)); - } - - realm.Add(realmSkin); - } - } - - db.Context.RemoveRange(existingSkins); - // Intentionally don't clean up the files, so they don't get purged by EF. - - transaction.Commit(); - } - } - - void migrateSettings(DatabaseWriteUsage db) - { - // migrate ruleset settings. can be removed 20220315. - var existingSettings = db.Context.DatabasedSetting; - - // previous entries in EF are removed post migration. - if (!existingSettings.Any()) - return; - - using (var realm = CreateContext()) - using (var transaction = realm.BeginWrite()) - { - // only migrate data if the realm database is empty. - if (!realm.All().Any()) - { - foreach (var dkb in existingSettings) - { - if (dkb.RulesetID == null) - continue; - - string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); - - if (string.IsNullOrEmpty(shortName)) - continue; - - realm.Add(new RealmRulesetSetting - { - Key = dkb.Key, - Value = dkb.StringValue, - RulesetName = shortName, - Variant = dkb.Variant ?? 0, - }); - } - } - - db.Context.RemoveRange(existingSettings); - - transaction.Commit(); - } - } - } - private void onMigration(Migration migration, ulong lastSchemaVersion) { for (ulong i = lastSchemaVersion + 1; i <= schema_version; i++) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4bae6f3c1d..f6e2c780ed 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -198,6 +198,8 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", contextFactory)); + new EFToRealmMigrator(contextFactory, realmFactory, LocalConfig).Run(); + dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")));