1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Merge pull request #24081 from Cootz/restore-scores-after-bearmap-reinstallation

Reattach any orphaned scores when a beatmap is imported
This commit is contained in:
Bartłomiej Dach 2023-07-01 22:33:38 +02:00 committed by GitHub
commit d017be50f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 12 deletions

View File

@ -18,6 +18,7 @@ using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Tests.Resources;
using Realms;
using SharpCompress.Archives;
@ -416,6 +417,53 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestImport_ThenModifyMapWithScore_ThenImport()
{
RunTestWithRealmAsync(async (realm, storage) =>
{
var importer = new BeatmapImporter(storage, realm);
using var store = new RealmRulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
var imported = await LoadOszIntoStore(importer, realm.Realm);
await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
// imitate making local changes via editor
// ReSharper disable once MethodHasAsyncOverload
realm.Write(_ =>
{
BeatmapInfo beatmap = imported.Beatmaps.First();
beatmap.Hash = "new_hash";
beatmap.ResetOnlineInfo();
});
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (see: https://github.com/ppy/osu/pull/22539).
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
Assert.That(imported.Beatmaps.First().Scores.Any());
var importedSecondTime = await importer.Import(new ImportTask(temp));
EnsureLoaded(realm.Realm);
// check the newly "imported" beatmap is not the original.
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
Assert.That(imported.ID != importedSecondTime.ID);
var importedFirstTimeBeatmap = imported.Beatmaps.First();
var importedSecondTimeBeatmap = importedSecondTime.PerformRead(s => s.Beatmaps.First());
Assert.That(importedFirstTimeBeatmap.ID != importedSecondTimeBeatmap.ID);
Assert.That(importedFirstTimeBeatmap.Hash != importedSecondTimeBeatmap.Hash);
Assert.That(!importedFirstTimeBeatmap.Scores.Any());
Assert.That(importedSecondTimeBeatmap.Scores.Count() == 1);
});
}
[Test]
public void TestImportThenImportWithChangedFile()
{
@ -1074,18 +1122,16 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
}
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap)
{
// TODO: reimplement when we have score support in realm.
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
// {
// OnlineID = 2,
// Beatmap = beatmap,
// BeatmapInfoID = beatmap.ID
// }, new ImportScoreTest.TestArchiveReader());
return Task.CompletedTask;
}
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) =>
realm.WriteAsync(() =>
{
realm.Add(new ScoreInfo
{
OnlineID = 2,
BeatmapInfo = beatmap,
BeatmapHash = beatmap.Hash
});
});
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
{

View File

@ -347,6 +347,73 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestDanglingScoreTransferred()
{
RunTestWithRealmAsync(async (realm, storage) =>
{
var importer = new BeatmapImporter(storage, realm);
using var rulesets = new RealmRulesetStore(realm, storage);
using var __ = getBeatmapArchive(out string pathOriginal);
using var _ = getBeatmapArchive(out string pathOnlineCopy);
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
Assert.That(importBeforeUpdate, Is.Not.Null);
Debug.Assert(importBeforeUpdate != null);
string scoreTargetBeatmapHash = string.Empty;
// set a score on the beatmap
importBeforeUpdate.PerformWrite(s =>
{
var beatmapInfo = s.Beatmaps.First();
scoreTargetBeatmapHash = beatmapInfo.Hash;
s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All<RulesetInfo>().First(), new RealmUser()));
});
// locally modify beatmap
const string new_beatmap_hash = "new_hash";
importBeforeUpdate.PerformWrite(s =>
{
var beatmapInfo = s.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash);
beatmapInfo.Hash = new_beatmap_hash;
beatmapInfo.ResetOnlineInfo();
});
realm.Run(r => r.Refresh());
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (https://github.com/ppy/osu/pull/22539).
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
checkCount<ScoreInfo>(realm, 1);
// reimport the original beatmap before local modifications
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value);
Assert.That(importAfterUpdate, Is.Not.Null);
Debug.Assert(importAfterUpdate != null);
realm.Run(r => r.Refresh());
// both original and locally modified versions present
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
checkCount<BeatmapSetInfo>(realm, 2);
// score is preserved
checkCount<ScoreInfo>(realm, 1);
// score is transferred to new beatmap
Assert.That(importBeforeUpdate.Value.Beatmaps.First(b => b.Hash == new_beatmap_hash).Scores, Has.Count.EqualTo(0));
Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1));
});
}
[Test]
public void TestScoreLostOnModification()
{

View File

@ -20,6 +20,7 @@ using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using Realms;
namespace osu.Game.Beatmaps
@ -204,6 +205,15 @@ namespace osu.Game.Beatmaps
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
{
base.PostImport(model, realm, parameters);
// Scores are stored separately from beatmaps, and persist even when a beatmap is modified or deleted.
// Let's reattach any matching scores that exist in the database, based on hash.
foreach (BeatmapInfo beatmap in model.Beatmaps)
{
foreach (var score in realm.All<ScoreInfo>().Where(score => score.BeatmapHash == beatmap.Hash))
score.BeatmapInfo = beatmap;
}
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
}

View File

@ -82,6 +82,7 @@ namespace osu.Game.Scoring
{
Ruleset = ruleset ?? new RulesetInfo();
BeatmapInfo = beatmap ?? new BeatmapInfo();
BeatmapHash = BeatmapInfo.Hash;
RealmUser = realmUser ?? new RealmUser();
ID = Guid.NewGuid();
}