mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 00:02:56 +08:00
Show realm migration progress at Loader
This commit is contained in:
parent
5be41a189b
commit
5622d2ba4f
@ -4,52 +4,130 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
internal class EFToRealmMigrator
|
internal class EFToRealmMigrator : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly DatabaseContextFactory efContextFactory;
|
public bool FinishedMigrating { get; private set; }
|
||||||
private readonly RealmContextFactory realmContextFactory;
|
|
||||||
private readonly OsuConfigManager config;
|
|
||||||
private readonly Storage storage;
|
|
||||||
|
|
||||||
public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage)
|
[Resolved]
|
||||||
|
private DatabaseContextFactory efContextFactory { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RealmContextFactory realmContextFactory { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Storage storage { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly OsuSpriteText currentOperationText;
|
||||||
|
|
||||||
|
public EFToRealmMigrator()
|
||||||
{
|
{
|
||||||
this.efContextFactory = efContextFactory;
|
RelativeSizeAxes = Axes.Both;
|
||||||
this.realmContextFactory = realmContextFactory;
|
|
||||||
this.config = config;
|
InternalChildren = new Drawable[]
|
||||||
this.storage = storage;
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Database migration in progress",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "This could take a few minutes depending on the speed of your disk(s).",
|
||||||
|
Font = OsuFont.Default.With(size: 30)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Please keep the window open until this completes!",
|
||||||
|
Font = OsuFont.Default.With(size: 30)
|
||||||
|
},
|
||||||
|
new LoadingSpinner(true)
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Visible }
|
||||||
|
},
|
||||||
|
currentOperationText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(size: 30)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// needs to be run on the update thread because of realm BlockAllOperations.
|
||||||
|
// maybe we can work around this? not sure..
|
||||||
createBackup();
|
createBackup();
|
||||||
|
|
||||||
using (var ef = efContextFactory.Get())
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
migrateSettings(ef);
|
using (var ef = efContextFactory.Get())
|
||||||
migrateSkins(ef);
|
{
|
||||||
migrateBeatmaps(ef);
|
migrateSettings(ef);
|
||||||
migrateScores(ef);
|
migrateSkins(ef);
|
||||||
}
|
migrateBeatmaps(ef);
|
||||||
|
migrateScores(ef);
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the database permanently.
|
// Delete the database permanently.
|
||||||
// Will cause future startups to not attempt migration.
|
// Will cause future startups to not attempt migration.
|
||||||
Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database);
|
log("Migration successful, deleting EF database");
|
||||||
efContextFactory.ResetDatabase();
|
efContextFactory.ResetDatabase();
|
||||||
|
}, TaskCreationOptions.LongRunning).ContinueWith(t =>
|
||||||
|
{
|
||||||
|
FinishedMigrating = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(string message)
|
||||||
|
{
|
||||||
|
Logger.Log(message, LoggingTarget.Database);
|
||||||
|
Scheduler.AddOnce(m => currentOperationText.Text = m, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateBeatmaps(OsuDbContext ef)
|
private void migrateBeatmaps(OsuDbContext ef)
|
||||||
@ -62,12 +140,12 @@ namespace osu.Game.Database
|
|||||||
.Include(s => s.Files).ThenInclude(f => f.FileInfo)
|
.Include(s => s.Files).ThenInclude(f => f.FileInfo)
|
||||||
.Include(s => s.Metadata);
|
.Include(s => s.Metadata);
|
||||||
|
|
||||||
Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database);
|
log("Beginning beatmaps migration to realm");
|
||||||
|
|
||||||
// previous entries in EF are removed post migration.
|
// previous entries in EF are removed post migration.
|
||||||
if (!existingBeatmapSets.Any())
|
if (!existingBeatmapSets.Any())
|
||||||
{
|
{
|
||||||
Logger.Log("No beatmaps found to migrate", LoggingTarget.Database);
|
log("No beatmaps found to migrate");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,13 +153,13 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
using (var realm = realmContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database);
|
log($"Found {count} beatmaps in EF");
|
||||||
|
|
||||||
// only migrate data if the realm database is empty.
|
// only migrate data if the realm database is empty.
|
||||||
// note that this cannot be written as: `realm.All<BeatmapSetInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
// note that this cannot be written as: `realm.All<BeatmapSetInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
||||||
if (realm.All<BeatmapSetInfo>().Any(s => !s.Protected))
|
if (realm.All<BeatmapSetInfo>().Any(s => !s.Protected))
|
||||||
{
|
{
|
||||||
Logger.Log("Skipping migration as realm already has beatmaps loaded", LoggingTarget.Database);
|
log("Skipping migration as realm already has beatmaps loaded");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -96,7 +174,7 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
transaction = realm.BeginWrite();
|
transaction = realm.BeginWrite();
|
||||||
Logger.Log($"Migrated {written}/{count} beatmaps...", LoggingTarget.Database);
|
log($"Migrated {written}/{count} beatmaps...");
|
||||||
}
|
}
|
||||||
|
|
||||||
var realmBeatmapSet = new BeatmapSetInfo
|
var realmBeatmapSet = new BeatmapSetInfo
|
||||||
@ -156,7 +234,7 @@ namespace osu.Game.Database
|
|||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database);
|
log($"Successfully migrated {count} beatmaps to realm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,12 +271,12 @@ namespace osu.Game.Database
|
|||||||
.Include(s => s.Files)
|
.Include(s => s.Files)
|
||||||
.ThenInclude(f => f.FileInfo);
|
.ThenInclude(f => f.FileInfo);
|
||||||
|
|
||||||
Logger.Log("Beginning scores migration to realm", LoggingTarget.Database);
|
log("Beginning scores migration to realm");
|
||||||
|
|
||||||
// previous entries in EF are removed post migration.
|
// previous entries in EF are removed post migration.
|
||||||
if (!existingScores.Any())
|
if (!existingScores.Any())
|
||||||
{
|
{
|
||||||
Logger.Log("No scores found to migrate", LoggingTarget.Database);
|
log("No scores found to migrate");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,12 +284,12 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
using (var realm = realmContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
Logger.Log($"Found {count} scores in EF", LoggingTarget.Database);
|
log($"Found {count} scores in EF");
|
||||||
|
|
||||||
// only migrate data if the realm database is empty.
|
// only migrate data if the realm database is empty.
|
||||||
if (realm.All<ScoreInfo>().Any())
|
if (realm.All<ScoreInfo>().Any())
|
||||||
{
|
{
|
||||||
Logger.Log("Skipping migration as realm already has scores loaded", LoggingTarget.Database);
|
log("Skipping migration as realm already has scores loaded");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -226,7 +304,7 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
transaction = realm.BeginWrite();
|
transaction = realm.BeginWrite();
|
||||||
Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database);
|
log($"Migrated {written}/{count} scores...");
|
||||||
}
|
}
|
||||||
|
|
||||||
var beatmap = realm.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
var beatmap = realm.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
||||||
@ -270,7 +348,7 @@ namespace osu.Game.Database
|
|||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database);
|
log($"Successfully migrated {count} scores to realm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,7 +386,7 @@ namespace osu.Game.Database
|
|||||||
// note that this cannot be written as: `realm.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
// note that this cannot be written as: `realm.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
||||||
if (!realm.All<SkinInfo>().Any(s => !s.Protected))
|
if (!realm.All<SkinInfo>().Any(s => !s.Protected))
|
||||||
{
|
{
|
||||||
Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database);
|
log($"Migrating {existingSkins.Count} skins");
|
||||||
|
|
||||||
foreach (var skin in existingSkins)
|
foreach (var skin in existingSkins)
|
||||||
{
|
{
|
||||||
@ -356,7 +434,7 @@ namespace osu.Game.Database
|
|||||||
if (!existingSettings.Any())
|
if (!existingSettings.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Logger.Log("Beginning settings migration to realm", LoggingTarget.Database);
|
log("Beginning settings migration to realm");
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
using (var realm = realmContextFactory.CreateContext())
|
||||||
using (var transaction = realm.BeginWrite())
|
using (var transaction = realm.BeginWrite())
|
||||||
@ -364,7 +442,7 @@ namespace osu.Game.Database
|
|||||||
// only migrate data if the realm database is empty.
|
// only migrate data if the realm database is empty.
|
||||||
if (!realm.All<RealmRulesetSetting>().Any())
|
if (!realm.All<RealmRulesetSetting>().Any())
|
||||||
{
|
{
|
||||||
Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database);
|
log($"Migrating {existingSettings.Count} settings");
|
||||||
|
|
||||||
foreach (var dkb in existingSettings)
|
foreach (var dkb in existingSettings)
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Screens.Menu;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using IntroSequence = osu.Game.Configuration.IntroSequence;
|
using IntroSequence = osu.Game.Configuration.IntroSequence;
|
||||||
|
|
||||||
@ -63,6 +64,11 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
|
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DatabaseContextFactory efContextFactory { get; set; }
|
||||||
|
|
||||||
|
private EFToRealmMigrator realmMigrator;
|
||||||
|
|
||||||
public override void OnEntering(IScreen last)
|
public override void OnEntering(IScreen last)
|
||||||
{
|
{
|
||||||
base.OnEntering(last);
|
base.OnEntering(last);
|
||||||
@ -70,6 +76,10 @@ namespace osu.Game.Screens
|
|||||||
LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal);
|
LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal);
|
||||||
LoadComponentAsync(loadableScreen = CreateLoadableScreen());
|
LoadComponentAsync(loadableScreen = CreateLoadableScreen());
|
||||||
|
|
||||||
|
// A non-null context factory means there's still content to migrate.
|
||||||
|
if (efContextFactory != null)
|
||||||
|
LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal);
|
||||||
|
|
||||||
LoadComponentAsync(spinner = new LoadingSpinner(true, true)
|
LoadComponentAsync(spinner = new LoadingSpinner(true, true)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
@ -86,7 +96,7 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
private void checkIfLoaded()
|
private void checkIfLoaded()
|
||||||
{
|
{
|
||||||
if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling)
|
if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false)
|
||||||
{
|
{
|
||||||
Schedule(checkIfLoaded);
|
Schedule(checkIfLoaded);
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user