mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 18:23:04 +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:
commit
94ad7abfd6
@ -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,87 +162,78 @@ 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.
|
var transaction = realm.BeginWrite();
|
||||||
// note that this cannot be written as: `realm.All<BeatmapSetInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
int written = 0;
|
||||||
if (realm.All<BeatmapSetInfo>().Any(s => !s.Protected))
|
|
||||||
{
|
|
||||||
log("Skipping migration as realm already has beatmaps loaded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var transaction = realm.BeginWrite();
|
|
||||||
int written = 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
foreach (var beatmapSet in existingBeatmapSets)
|
||||||
{
|
{
|
||||||
foreach (var beatmapSet in existingBeatmapSets)
|
if (++written % 1000 == 0)
|
||||||
{
|
{
|
||||||
if (++written % 1000 == 0)
|
transaction.Commit();
|
||||||
{
|
transaction = realm.BeginWrite();
|
||||||
transaction.Commit();
|
log($"Migrated {written}/{count} beatmaps...");
|
||||||
transaction = realm.BeginWrite();
|
}
|
||||||
log($"Migrated {written}/{count} beatmaps...");
|
|
||||||
}
|
|
||||||
|
|
||||||
var realmBeatmapSet = new BeatmapSetInfo
|
var realmBeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineID = beatmapSet.OnlineID ?? -1,
|
||||||
|
DateAdded = beatmapSet.DateAdded,
|
||||||
|
Status = beatmapSet.Status,
|
||||||
|
DeletePending = beatmapSet.DeletePending,
|
||||||
|
Hash = beatmapSet.Hash,
|
||||||
|
Protected = beatmapSet.Protected,
|
||||||
|
};
|
||||||
|
|
||||||
|
migrateFiles(beatmapSet, realm, realmBeatmapSet);
|
||||||
|
|
||||||
|
foreach (var beatmap in beatmapSet.Beatmaps)
|
||||||
|
{
|
||||||
|
var ruleset = realm.Find<RulesetInfo>(beatmap.RulesetInfo.ShortName);
|
||||||
|
var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata);
|
||||||
|
|
||||||
|
var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata)
|
||||||
{
|
{
|
||||||
OnlineID = beatmapSet.OnlineID ?? -1,
|
DifficultyName = beatmap.DifficultyName,
|
||||||
DateAdded = beatmapSet.DateAdded,
|
Status = beatmap.Status,
|
||||||
Status = beatmapSet.Status,
|
OnlineID = beatmap.OnlineID ?? -1,
|
||||||
DeletePending = beatmapSet.DeletePending,
|
Length = beatmap.Length,
|
||||||
Hash = beatmapSet.Hash,
|
BPM = beatmap.BPM,
|
||||||
Protected = beatmapSet.Protected,
|
Hash = beatmap.Hash,
|
||||||
|
StarRating = beatmap.StarRating,
|
||||||
|
MD5Hash = beatmap.MD5Hash,
|
||||||
|
Hidden = beatmap.Hidden,
|
||||||
|
AudioLeadIn = beatmap.AudioLeadIn,
|
||||||
|
StackLeniency = beatmap.StackLeniency,
|
||||||
|
SpecialStyle = beatmap.SpecialStyle,
|
||||||
|
LetterboxInBreaks = beatmap.LetterboxInBreaks,
|
||||||
|
WidescreenStoryboard = beatmap.WidescreenStoryboard,
|
||||||
|
EpilepsyWarning = beatmap.EpilepsyWarning,
|
||||||
|
SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate,
|
||||||
|
DistanceSpacing = beatmap.DistanceSpacing,
|
||||||
|
BeatDivisor = beatmap.BeatDivisor,
|
||||||
|
GridSize = beatmap.GridSize,
|
||||||
|
TimelineZoom = beatmap.TimelineZoom,
|
||||||
|
Countdown = beatmap.Countdown,
|
||||||
|
CountdownOffset = beatmap.CountdownOffset,
|
||||||
|
MaxCombo = beatmap.MaxCombo,
|
||||||
|
Bookmarks = beatmap.Bookmarks,
|
||||||
|
BeatmapSet = realmBeatmapSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateFiles(beatmapSet, realm, realmBeatmapSet);
|
realmBeatmapSet.Beatmaps.Add(realmBeatmap);
|
||||||
|
|
||||||
foreach (var beatmap in beatmapSet.Beatmaps)
|
|
||||||
{
|
|
||||||
var ruleset = realm.Find<RulesetInfo>(beatmap.RulesetInfo.ShortName);
|
|
||||||
var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata);
|
|
||||||
|
|
||||||
var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata)
|
|
||||||
{
|
|
||||||
DifficultyName = beatmap.DifficultyName,
|
|
||||||
Status = beatmap.Status,
|
|
||||||
OnlineID = beatmap.OnlineID ?? -1,
|
|
||||||
Length = beatmap.Length,
|
|
||||||
BPM = beatmap.BPM,
|
|
||||||
Hash = beatmap.Hash,
|
|
||||||
StarRating = beatmap.StarRating,
|
|
||||||
MD5Hash = beatmap.MD5Hash,
|
|
||||||
Hidden = beatmap.Hidden,
|
|
||||||
AudioLeadIn = beatmap.AudioLeadIn,
|
|
||||||
StackLeniency = beatmap.StackLeniency,
|
|
||||||
SpecialStyle = beatmap.SpecialStyle,
|
|
||||||
LetterboxInBreaks = beatmap.LetterboxInBreaks,
|
|
||||||
WidescreenStoryboard = beatmap.WidescreenStoryboard,
|
|
||||||
EpilepsyWarning = beatmap.EpilepsyWarning,
|
|
||||||
SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate,
|
|
||||||
DistanceSpacing = beatmap.DistanceSpacing,
|
|
||||||
BeatDivisor = beatmap.BeatDivisor,
|
|
||||||
GridSize = beatmap.GridSize,
|
|
||||||
TimelineZoom = beatmap.TimelineZoom,
|
|
||||||
Countdown = beatmap.Countdown,
|
|
||||||
CountdownOffset = beatmap.CountdownOffset,
|
|
||||||
MaxCombo = beatmap.MaxCombo,
|
|
||||||
Bookmarks = beatmap.Bookmarks,
|
|
||||||
BeatmapSet = realmBeatmapSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
realmBeatmapSet.Beatmaps.Add(realmBeatmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
realm.Add(realmBeatmapSet);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
log($"Successfully migrated {count} beatmaps to realm");
|
realm.Add(realmBeatmapSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
log($"Successfully migrated {count} beatmaps to realm");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,70 +284,62 @@ 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.
|
var transaction = realm.BeginWrite();
|
||||||
if (realm.All<ScoreInfo>().Any())
|
int written = 0;
|
||||||
{
|
|
||||||
log("Skipping migration as realm already has scores loaded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var transaction = realm.BeginWrite();
|
|
||||||
int written = 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
foreach (var score in existingScores)
|
||||||
{
|
{
|
||||||
foreach (var score in existingScores)
|
if (++written % 1000 == 0)
|
||||||
{
|
{
|
||||||
if (++written % 1000 == 0)
|
transaction.Commit();
|
||||||
{
|
transaction = realm.BeginWrite();
|
||||||
transaction.Commit();
|
log($"Migrated {written}/{count} scores...");
|
||||||
transaction = realm.BeginWrite();
|
|
||||||
log($"Migrated {written}/{count} scores...");
|
|
||||||
}
|
|
||||||
|
|
||||||
var beatmap = realm.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
|
||||||
var ruleset = realm.Find<RulesetInfo>(score.Ruleset.ShortName);
|
|
||||||
var user = new RealmUser
|
|
||||||
{
|
|
||||||
OnlineID = score.User.OnlineID,
|
|
||||||
Username = score.User.Username
|
|
||||||
};
|
|
||||||
|
|
||||||
var realmScore = new ScoreInfo(beatmap, ruleset, user)
|
|
||||||
{
|
|
||||||
Hash = score.Hash,
|
|
||||||
DeletePending = score.DeletePending,
|
|
||||||
OnlineID = score.OnlineID ?? -1,
|
|
||||||
ModsJson = score.ModsJson,
|
|
||||||
StatisticsJson = score.StatisticsJson,
|
|
||||||
TotalScore = score.TotalScore,
|
|
||||||
MaxCombo = score.MaxCombo,
|
|
||||||
Accuracy = score.Accuracy,
|
|
||||||
HasReplay = ((IScoreInfo)score).HasReplay,
|
|
||||||
Date = score.Date,
|
|
||||||
PP = score.PP,
|
|
||||||
Rank = score.Rank,
|
|
||||||
HitEvents = score.HitEvents,
|
|
||||||
Passed = score.Passed,
|
|
||||||
Combo = score.Combo,
|
|
||||||
Position = score.Position,
|
|
||||||
Statistics = score.Statistics,
|
|
||||||
Mods = score.Mods,
|
|
||||||
APIMods = score.APIMods,
|
|
||||||
};
|
|
||||||
|
|
||||||
migrateFiles(score, realm, realmScore);
|
|
||||||
|
|
||||||
realm.Add(realmScore);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
log($"Successfully migrated {count} scores to realm");
|
var beatmap = realm.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
||||||
|
var ruleset = realm.Find<RulesetInfo>(score.Ruleset.ShortName);
|
||||||
|
var user = new RealmUser
|
||||||
|
{
|
||||||
|
OnlineID = score.User.OnlineID,
|
||||||
|
Username = score.User.Username
|
||||||
|
};
|
||||||
|
|
||||||
|
var realmScore = new ScoreInfo(beatmap, ruleset, user)
|
||||||
|
{
|
||||||
|
Hash = score.Hash,
|
||||||
|
DeletePending = score.DeletePending,
|
||||||
|
OnlineID = score.OnlineID ?? -1,
|
||||||
|
ModsJson = score.ModsJson,
|
||||||
|
StatisticsJson = score.StatisticsJson,
|
||||||
|
TotalScore = score.TotalScore,
|
||||||
|
MaxCombo = score.MaxCombo,
|
||||||
|
Accuracy = score.Accuracy,
|
||||||
|
HasReplay = ((IScoreInfo)score).HasReplay,
|
||||||
|
Date = score.Date,
|
||||||
|
PP = score.PP,
|
||||||
|
Rank = score.Rank,
|
||||||
|
HitEvents = score.HitEvents,
|
||||||
|
Passed = score.Passed,
|
||||||
|
Combo = score.Combo,
|
||||||
|
Position = score.Position,
|
||||||
|
Statistics = score.Statistics,
|
||||||
|
Mods = score.Mods,
|
||||||
|
APIMods = score.APIMods,
|
||||||
|
};
|
||||||
|
|
||||||
|
migrateFiles(score, realm, realmScore);
|
||||||
|
|
||||||
|
realm.Add(realmScore);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
log($"Successfully migrated {count} scores to realm");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user