1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 21:52:55 +08:00

Merge pull request #16590 from peppy/realm-migrate-on-failure

Allow realm migration to run again if interrupted halfway
This commit is contained in:
Dan Balasescu 2022-01-24 20:19:52 +09:00 committed by GitHub
commit 94ad7abfd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 133 deletions

View File

@ -27,7 +27,9 @@ namespace osu.Game.Database
{ {
internal class EFToRealmMigrator : CompositeDrawable internal class EFToRealmMigrator : CompositeDrawable
{ {
public bool FinishedMigrating { get; private set; } public Task<bool> MigrationCompleted => migrationCompleted.Task;
private readonly TaskCompletionSource<bool> migrationCompleted = new TaskCompletionSource<bool>();
[Resolved] [Resolved]
private DatabaseContextFactory efContextFactory { get; set; } = null!; private DatabaseContextFactory efContextFactory { get; set; } = null!;
@ -99,6 +101,17 @@ namespace osu.Game.Database
{ {
using (var ef = efContextFactory.Get()) using (var ef = efContextFactory.Get())
{ {
realmContextFactory.Write(realm =>
{
// Before beginning, ensure realm is in an empty state.
// Migrations which are half-completed could lead to issues if the user tries a second time.
// Note that we only do this for beatmaps and scores since the other migrations are yonks old.
realm.RemoveAll<BeatmapSetInfo>();
realm.RemoveAll<BeatmapInfo>();
realm.RemoveAll<BeatmapMetadata>();
realm.RemoveAll<ScoreInfo>();
});
migrateSettings(ef); migrateSettings(ef);
migrateSkins(ef); migrateSkins(ef);
migrateBeatmaps(ef); migrateBeatmaps(ef);
@ -114,7 +127,7 @@ namespace osu.Game.Database
Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important);
}, TaskCreationOptions.LongRunning).ContinueWith(t => }, TaskCreationOptions.LongRunning).ContinueWith(t =>
{ {
FinishedMigrating = true; migrationCompleted.SetResult(true);
}); });
} }
@ -149,14 +162,6 @@ namespace osu.Game.Database
{ {
log($"Found {count} beatmaps in EF"); log($"Found {count} beatmaps in EF");
// 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()`.
if (realm.All<BeatmapSetInfo>().Any(s => !s.Protected))
{
log("Skipping migration as realm already has beatmaps loaded");
}
else
{
var transaction = realm.BeginWrite(); var transaction = realm.BeginWrite();
int written = 0; int written = 0;
@ -229,7 +234,6 @@ namespace osu.Game.Database
} }
log($"Successfully migrated {count} beatmaps to realm"); log($"Successfully migrated {count} beatmaps to realm");
}
}); });
} }
@ -280,13 +284,6 @@ namespace osu.Game.Database
{ {
log($"Found {count} scores in EF"); log($"Found {count} scores in EF");
// only migrate data if the realm database is empty.
if (realm.All<ScoreInfo>().Any())
{
log("Skipping migration as realm already has scores loaded");
}
else
{
var transaction = realm.BeginWrite(); var transaction = realm.BeginWrite();
int written = 0; int written = 0;
@ -343,7 +340,6 @@ namespace osu.Game.Database
} }
log($"Successfully migrated {count} scores to realm"); log($"Successfully migrated {count} scores to realm");
}
}); });
} }

View File

@ -74,11 +74,22 @@ namespace osu.Game.Screens
base.OnEntering(last); base.OnEntering(last);
LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal);
LoadComponentAsync(loadableScreen = CreateLoadableScreen());
// A non-null context factory means there's still content to migrate. // A non-null context factory means there's still content to migrate.
if (efContextFactory != null) if (efContextFactory != null)
{
LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal);
realmMigrator.MigrationCompleted.ContinueWith(_ => Schedule(() =>
{
// Delay initial screen loading to ensure that the migration is in a complete and sane state
// before the intro screen may import the game intro beatmap.
LoadComponentAsync(loadableScreen = CreateLoadableScreen());
}));
}
else
{
LoadComponentAsync(loadableScreen = CreateLoadableScreen());
}
LoadComponentAsync(spinner = new LoadingSpinner(true, true) LoadComponentAsync(spinner = new LoadingSpinner(true, true)
{ {
@ -96,7 +107,7 @@ namespace osu.Game.Screens
private void checkIfLoaded() private void checkIfLoaded()
{ {
if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false) if (loadableScreen?.LoadState != LoadState.Ready || !precompiler.FinishedCompiling)
{ {
Schedule(checkIfLoaded); Schedule(checkIfLoaded);
return; return;