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:
commit
d017be50f1
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user