2022-07-26 13:53:20 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2022-07-26 15:30:44 +08:00
using System ;
2022-07-26 13:53:20 +08:00
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
2022-07-26 15:30:44 +08:00
using System.Linq.Expressions ;
2022-07-26 13:53:20 +08:00
using NUnit.Framework ;
2022-07-26 15:30:44 +08:00
using osu.Framework.Allocation ;
2022-07-26 13:53:20 +08:00
using osu.Game.Beatmaps ;
2022-07-28 14:02:58 +08:00
using osu.Game.Collections ;
2022-07-26 13:53:20 +08:00
using osu.Game.Database ;
2022-07-26 16:22:52 +08:00
using osu.Game.Models ;
2022-07-26 13:53:20 +08:00
using osu.Game.Overlays.Notifications ;
using osu.Game.Rulesets ;
2022-07-26 16:22:52 +08:00
using osu.Game.Scoring ;
2022-07-26 13:53:20 +08:00
using osu.Game.Tests.Resources ;
2022-07-26 15:30:44 +08:00
using Realms ;
2022-07-26 13:53:20 +08:00
using SharpCompress.Archives ;
using SharpCompress.Archives.Zip ;
using SharpCompress.Common ;
using SharpCompress.Writers.Zip ;
namespace osu.Game.Tests.Database
{
/// <summary>
/// Tests the flow where a beatmap is already loaded and an update is applied.
/// </summary>
[TestFixture]
public class BeatmapImporterUpdateTests : RealmTest
{
2022-07-26 16:22:52 +08:00
private const int count_beatmaps = 12 ;
2022-07-26 13:53:20 +08:00
[Test]
2022-07-26 15:30:44 +08:00
public void TestNewDifficultyAdded ( )
2022-07-26 13:53:20 +08:00
{
RunTestWithRealmAsync ( async ( realm , storage ) = >
{
var importer = new BeatmapImporter ( storage , realm ) ;
2022-07-26 15:30:44 +08:00
using var rulesets = new RealmRulesetStore ( realm , storage ) ;
2022-07-26 13:53:20 +08:00
2022-07-26 15:30:44 +08:00
using var __ = getBeatmapArchive ( out string pathOriginal ) ;
using var _ = getBeatmapArchiveWithModifications ( out string pathMissingOneBeatmap , directory = >
{
// remove one difficulty before first import
directory . GetFiles ( "*.osu" ) . First ( ) . Delete ( ) ;
} ) ;
2022-07-26 13:53:20 +08:00
2022-07-26 15:30:44 +08:00
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathMissingOneBeatmap ) ) ;
2022-07-26 13:53:20 +08:00
2022-07-26 15:30:44 +08:00
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
2022-07-26 13:53:20 +08:00
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 15:30:44 +08:00
checkCount < BeatmapSetInfo > ( realm , 1 , s = > ! s . DeletePending ) ;
2022-07-26 16:27:23 +08:00
Assert . That ( importBeforeUpdate . Value . Beatmaps , Has . Count . EqualTo ( count_beatmaps - 1 ) ) ;
// Second import matches first but contains one extra .osu file.
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathOriginal ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:27:23 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps ) ;
checkCount < BeatmapSetInfo > ( realm , 1 ) ;
// check the newly "imported" beatmap is not the original.
Assert . That ( importBeforeUpdate . ID , Is . Not . EqualTo ( importAfterUpdate . ID ) ) ;
// Previous beatmap set has no beatmaps so will be completely purged on the spot.
Assert . That ( importBeforeUpdate . Value . IsValid , Is . False ) ;
} ) ;
}
/// <summary>
/// Regression test covering https://github.com/ppy/osu/issues/19369 (import potentially duplicating if original has no <see cref="BeatmapInfo.OnlineID"/>).
/// </summary>
[Test]
public void TestNewDifficultyAddedNoOnlineID ( )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathMissingOneBeatmap , directory = >
{
// remove one difficulty before first import
directory . GetFiles ( "*.osu" ) . First ( ) . Delete ( ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathMissingOneBeatmap ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
// This test is the same as TestNewDifficultyAdded except for this block.
importBeforeUpdate . PerformWrite ( s = >
{
s . OnlineID = - 1 ;
foreach ( var beatmap in s . Beatmaps )
beatmap . ResetOnlineInfo ( ) ;
} ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:27:23 +08:00
checkCount < BeatmapSetInfo > ( realm , 1 , s = > ! s . DeletePending ) ;
2022-07-26 16:22:52 +08:00
Assert . That ( importBeforeUpdate . Value . Beatmaps , Has . Count . EqualTo ( count_beatmaps - 1 ) ) ;
2022-07-26 13:53:20 +08:00
2022-07-26 15:30:44 +08:00
// Second import matches first but contains one extra .osu file.
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathOriginal ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps ) ;
2022-07-26 15:30:44 +08:00
checkCount < BeatmapSetInfo > ( realm , 1 ) ;
// check the newly "imported" beatmap is not the original.
Assert . That ( importBeforeUpdate . ID , Is . Not . EqualTo ( importAfterUpdate . ID ) ) ;
2022-07-26 16:22:52 +08:00
// Previous beatmap set has no beatmaps so will be completely purged on the spot.
Assert . That ( importBeforeUpdate . Value . IsValid , Is . False ) ;
} ) ;
}
[Test]
public void TestExistingDifficultyModified ( )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathModified , directory = >
{
// Modify one .osu file with different content.
var firstOsuFile = directory . GetFiles ( "*.osu" ) . First ( ) ;
string existingContent = File . ReadAllText ( firstOsuFile . FullName ) ;
File . WriteAllText ( firstOsuFile . FullName , existingContent + "\n# I am new content" ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapSetInfo > ( realm , 1 , s = > ! s . DeletePending ) ;
Assert . That ( importBeforeUpdate . Value . Beatmaps , Has . Count . EqualTo ( count_beatmaps ) ) ;
// Second import matches first but contains one extra .osu file.
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathModified ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
// should only contain the modified beatmap (others purged).
Assert . That ( importBeforeUpdate . Value . Beatmaps , Has . Count . EqualTo ( 1 ) ) ;
Assert . That ( importAfterUpdate . Value . Beatmaps , Has . Count . EqualTo ( count_beatmaps ) ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps + 1 ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps + 1 ) ;
checkCount < BeatmapSetInfo > ( realm , 1 , s = > ! s . DeletePending ) ;
checkCount < BeatmapSetInfo > ( realm , 1 , s = > s . DeletePending ) ;
} ) ;
}
[Test]
public void TestExistingDifficultyRemoved ( )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathMissingOneBeatmap , directory = >
{
// remove one difficulty before first import
directory . GetFiles ( "*.osu" ) . First ( ) . Delete ( ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
Assert . That ( importBeforeUpdate . Value . Beatmaps , Has . Count . EqualTo ( count_beatmaps ) ) ;
Assert . That ( importBeforeUpdate . Value . Beatmaps . First ( ) . OnlineID , Is . GreaterThan ( - 1 ) ) ;
// Second import matches first but contains one extra .osu file.
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathMissingOneBeatmap ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps ) ;
checkCount < BeatmapSetInfo > ( realm , 2 ) ;
// previous set should contain the removed beatmap still.
Assert . That ( importBeforeUpdate . Value . Beatmaps , Has . Count . EqualTo ( 1 ) ) ;
Assert . That ( importBeforeUpdate . Value . Beatmaps . First ( ) . OnlineID , Is . EqualTo ( - 1 ) ) ;
// Previous beatmap set has no beatmaps so will be completely purged on the spot.
Assert . That ( importAfterUpdate . Value . Beatmaps , Has . Count . EqualTo ( count_beatmaps - 1 ) ) ;
} ) ;
}
[Test]
public void TestUpdatedImportContainsNothing ( )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathEmpty , directory = >
{
foreach ( var file in directory . GetFiles ( ) )
file . Delete ( ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathEmpty ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapSetInfo > ( realm , 1 ) ;
checkCount < BeatmapInfo > ( realm , count_beatmaps ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps ) ;
Assert . That ( importBeforeUpdate . Value . IsValid , Is . True ) ;
} ) ;
}
[Test]
public void TestNoChanges ( )
{
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 pathOriginalSecond ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathOriginalSecond ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapSetInfo > ( realm , 1 ) ;
checkCount < BeatmapInfo > ( realm , count_beatmaps ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps ) ;
Assert . That ( importBeforeUpdate . Value . Beatmaps . First ( ) . OnlineID , Is . GreaterThan ( - 1 ) ) ;
Assert . That ( importBeforeUpdate . ID , Is . EqualTo ( importAfterUpdate . ID ) ) ;
} ) ;
}
[Test]
public void TestScoreTransferredOnUnchanged ( )
{
RunTestWithRealmAsync ( async ( realm , storage ) = >
{
var importer = new BeatmapImporter ( storage , realm ) ;
using var rulesets = new RealmRulesetStore ( realm , storage ) ;
2022-07-27 15:27:10 +08:00
string removedFilename = null ! ;
2022-07-26 16:22:52 +08:00
using var __ = getBeatmapArchive ( out string pathOriginal ) ;
using var _ = getBeatmapArchiveWithModifications ( out string pathMissingOneBeatmap , directory = >
{
// arbitrary beatmap removal
2022-07-27 15:27:10 +08:00
var fileToRemove = directory . GetFiles ( "*.osu" ) . First ( ) ;
removedFilename = fileToRemove . Name ;
fileToRemove . Delete ( ) ;
2022-07-26 16:22:52 +08:00
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
string scoreTargetBeatmapHash = string . Empty ;
importBeforeUpdate . PerformWrite ( s = >
{
2022-07-27 15:27:10 +08:00
// make sure not to add scores to the same beatmap that is removed in the update.
var beatmapInfo = s . Beatmaps . First ( b = > b . File ? . Filename ! = removedFilename ) ;
2022-07-26 16:22:52 +08:00
scoreTargetBeatmapHash = beatmapInfo . Hash ;
2023-07-06 12:37:42 +08:00
s . Realm ! . Add ( new ScoreInfo ( beatmapInfo , s . Realm . All < RulesetInfo > ( ) . First ( ) , new RealmUser ( ) ) ) ;
2022-07-26 16:22:52 +08:00
} ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < ScoreInfo > ( realm , 1 ) ;
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathMissingOneBeatmap ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps ) ;
checkCount < BeatmapSetInfo > ( realm , 2 ) ;
// score is transferred across to the new set
checkCount < ScoreInfo > ( realm , 1 ) ;
Assert . That ( importAfterUpdate . Value . Beatmaps . First ( b = > b . Hash = = scoreTargetBeatmapHash ) . Scores , Has . Count . EqualTo ( 1 ) ) ;
} ) ;
}
2023-07-01 14:49:06 +08:00
[Test]
2023-07-02 02:38:00 +08:00
public void TestDanglingScoreTransferred ( )
2023-07-01 14:49:06 +08:00
{
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 ;
2023-07-02 02:37:33 +08:00
// set a score on the beatmap
2023-07-01 14:49:06 +08:00
importBeforeUpdate . PerformWrite ( s = >
{
var beatmapInfo = s . Beatmaps . First ( ) ;
scoreTargetBeatmapHash = beatmapInfo . Hash ;
2023-07-06 12:37:42 +08:00
s . Realm ! . Add ( new ScoreInfo ( beatmapInfo , s . Realm . All < RulesetInfo > ( ) . First ( ) , new RealmUser ( ) ) ) ;
2023-07-01 14:49:06 +08:00
} ) ;
2023-07-02 02:37:33 +08:00
// locally modify beatmap
2023-07-01 14:49:06 +08:00
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 ( ) ) ;
2023-07-02 02:37:33 +08:00
// 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.
2023-07-01 14:49:06 +08:00
checkCount < ScoreInfo > ( realm , 1 ) ;
2023-07-02 02:37:33 +08:00
// reimport the original beatmap before local modifications
2023-07-01 14:49:06 +08:00
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 ( ) ) ;
2023-07-02 02:37:33 +08:00
// both original and locally modified versions present
2023-07-01 14:49:06 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps + 1 ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps + 1 ) ;
checkCount < BeatmapSetInfo > ( realm , 2 ) ;
2023-07-02 02:37:33 +08:00
// score is preserved
2023-07-01 14:49:06 +08:00
checkCount < ScoreInfo > ( realm , 1 ) ;
2023-07-02 02:37:33 +08:00
// score is transferred to new beatmap
2023-07-01 14:49:06 +08:00
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 ) ) ;
} ) ;
}
2022-07-26 16:22:52 +08:00
[Test]
public void TestScoreLostOnModification ( )
{
RunTestWithRealmAsync ( async ( realm , storage ) = >
{
var importer = new BeatmapImporter ( storage , realm ) ;
using var rulesets = new RealmRulesetStore ( realm , storage ) ;
using var __ = getBeatmapArchive ( out string pathOriginal ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
string? scoreTargetFilename = string . Empty ;
importBeforeUpdate . PerformWrite ( s = >
{
var beatmapInfo = s . Beatmaps . Last ( ) ;
scoreTargetFilename = beatmapInfo . File ? . Filename ;
2023-07-06 12:37:42 +08:00
s . Realm ! . Add ( new ScoreInfo ( beatmapInfo , s . Realm . All < RulesetInfo > ( ) . First ( ) , new RealmUser ( ) ) ) ;
2022-07-26 16:22:52 +08:00
} ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < ScoreInfo > ( realm , 1 ) ;
using var _ = getBeatmapArchiveWithModifications ( out string pathModified , directory = >
{
// Modify one .osu file with different content.
var firstOsuFile = directory . GetFiles ( scoreTargetFilename ) . First ( ) ;
string existingContent = File . ReadAllText ( firstOsuFile . FullName ) ;
File . WriteAllText ( firstOsuFile . FullName , existingContent + "\n# I am new content" ) ;
} ) ;
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathModified ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
2022-07-28 14:15:41 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-26 16:22:52 +08:00
checkCount < BeatmapInfo > ( realm , count_beatmaps + 1 ) ;
checkCount < BeatmapMetadata > ( realm , count_beatmaps + 1 ) ;
checkCount < BeatmapSetInfo > ( realm , 2 ) ;
// score is not transferred due to modifications.
checkCount < ScoreInfo > ( realm , 1 ) ;
Assert . That ( importBeforeUpdate . Value . Beatmaps . AsEnumerable ( ) . First ( b = > b . File ? . Filename = = scoreTargetFilename ) . Scores , Has . Count . EqualTo ( 1 ) ) ;
Assert . That ( importAfterUpdate . Value . Beatmaps . AsEnumerable ( ) . First ( b = > b . File ? . Filename = = scoreTargetFilename ) . Scores , Has . Count . EqualTo ( 0 ) ) ;
} ) ;
}
[Test]
public void TestMetadataTransferred ( )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathMissingOneBeatmap , directory = >
{
// arbitrary beatmap removal
directory . GetFiles ( "*.osu" ) . First ( ) . Delete ( ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathMissingOneBeatmap ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
Assert . That ( importBeforeUpdate . ID , Is . Not . EqualTo ( importAfterUpdate . ID ) ) ;
Assert . That ( importBeforeUpdate . Value . DateAdded , Is . EqualTo ( importAfterUpdate . Value . DateAdded ) ) ;
2022-07-26 13:53:20 +08:00
} ) ;
}
2022-07-26 15:30:44 +08:00
2022-07-28 14:02:58 +08:00
/// <summary>
/// If all difficulties in the original beatmap set are in a collection, presume the user also wants new difficulties added.
/// </summary>
[TestCase(false)]
[TestCase(true)]
public void TestCollectionTransferNewBeatmap ( bool allOriginalBeatmapsInCollection )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathMissingOneBeatmap , directory = >
{
// remove one difficulty before first import
directory . GetFiles ( "*.osu" ) . First ( ) . Delete ( ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathMissingOneBeatmap ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
int beatmapsToAddToCollection = 0 ;
importBeforeUpdate . PerformWrite ( s = >
{
2023-07-06 12:37:42 +08:00
var beatmapCollection = s . Realm ! . Add ( new BeatmapCollection ( "test collection" ) ) ;
2022-07-28 14:02:58 +08:00
beatmapsToAddToCollection = s . Beatmaps . Count - ( allOriginalBeatmapsInCollection ? 0 : 1 ) ;
for ( int i = 0 ; i < beatmapsToAddToCollection ; i + + )
beatmapCollection . BeatmapMD5Hashes . Add ( s . Beatmaps [ i ] . MD5Hash ) ;
} ) ;
// Second import matches first but contains one extra .osu file.
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathOriginal ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
importAfterUpdate . PerformRead ( updated = >
{
2023-07-06 12:37:42 +08:00
updated . Realm ! . Refresh ( ) ;
2022-07-28 14:18:04 +08:00
2022-07-28 14:02:58 +08:00
string [ ] hashes = updated . Realm . All < BeatmapCollection > ( ) . Single ( ) . BeatmapMD5Hashes . ToArray ( ) ;
if ( allOriginalBeatmapsInCollection )
{
Assert . That ( updated . Beatmaps . Count , Is . EqualTo ( beatmapsToAddToCollection + 1 ) ) ;
Assert . That ( hashes , Has . Length . EqualTo ( updated . Beatmaps . Count ) ) ;
}
else
{
// Collection contains one less than the original beatmap, and two less after update (new difficulty included).
Assert . That ( updated . Beatmaps . Count , Is . EqualTo ( beatmapsToAddToCollection + 2 ) ) ;
Assert . That ( hashes , Has . Length . EqualTo ( beatmapsToAddToCollection ) ) ;
}
} ) ;
} ) ;
}
/// <summary>
/// If a difficulty in the original beatmap set is modified, the updated version should remain in any collections it was in.
/// </summary>
[Test]
public void TestCollectionTransferModifiedBeatmap ( )
{
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 _ = getBeatmapArchiveWithModifications ( out string pathModified , directory = >
{
// Modify one .osu file with different content.
var firstOsuFile = directory . GetFiles ( "*[Hard]*.osu" ) . First ( ) ;
string existingContent = File . ReadAllText ( firstOsuFile . FullName ) ;
File . WriteAllText ( firstOsuFile . FullName , existingContent + "\n# I am new content" ) ;
} ) ;
var importBeforeUpdate = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( importBeforeUpdate , Is . Not . Null ) ;
Debug . Assert ( importBeforeUpdate ! = null ) ;
string originalHash = string . Empty ;
importBeforeUpdate . PerformWrite ( s = >
{
2023-07-06 12:37:42 +08:00
var beatmapCollection = s . Realm ! . Add ( new BeatmapCollection ( "test collection" ) ) ;
2022-07-28 14:02:58 +08:00
originalHash = s . Beatmaps . Single ( b = > b . DifficultyName = = "Hard" ) . MD5Hash ;
beatmapCollection . BeatmapMD5Hashes . Add ( originalHash ) ;
} ) ;
2022-07-28 15:56:11 +08:00
// Second import matches first but contains a modified .osu file.
2022-07-28 14:02:58 +08:00
var importAfterUpdate = await importer . ImportAsUpdate ( new ProgressNotification ( ) , new ImportTask ( pathModified ) , importBeforeUpdate . Value ) ;
Assert . That ( importAfterUpdate , Is . Not . Null ) ;
Debug . Assert ( importAfterUpdate ! = null ) ;
importAfterUpdate . PerformRead ( updated = >
{
2023-07-06 12:37:42 +08:00
updated . Realm ! . Refresh ( ) ;
2022-07-28 14:18:04 +08:00
2022-07-28 14:02:58 +08:00
string [ ] hashes = updated . Realm . All < BeatmapCollection > ( ) . Single ( ) . BeatmapMD5Hashes . ToArray ( ) ;
string updatedHash = updated . Beatmaps . Single ( b = > b . DifficultyName = = "Hard" ) . MD5Hash ;
Assert . That ( hashes , Has . Length . EqualTo ( 1 ) ) ;
Assert . That ( hashes . First ( ) , Is . EqualTo ( updatedHash ) ) ;
Assert . That ( updatedHash , Is . Not . EqualTo ( originalHash ) ) ;
} ) ;
} ) ;
}
2022-07-26 15:30:44 +08:00
private static void checkCount < T > ( RealmAccess realm , int expected , Expression < Func < T , bool > > ? condition = null ) where T : RealmObject
{
var query = realm . Realm . All < T > ( ) ;
if ( condition ! = null )
query = query . Where ( condition ) ;
Assert . That ( query , Has . Count . EqualTo ( expected ) ) ;
}
private static IDisposable getBeatmapArchiveWithModifications ( out string path , Action < DirectoryInfo > applyModifications )
{
var cleanup = getBeatmapArchive ( out path ) ;
string extractedFolder = $"{path}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
using ( var zip = ZipArchive . Open ( path ) )
zip . WriteToDirectory ( extractedFolder ) ;
applyModifications ( new DirectoryInfo ( extractedFolder ) ) ;
File . Delete ( path ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( path , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
Directory . Delete ( extractedFolder , true ) ;
return cleanup ;
}
private static IDisposable getBeatmapArchive ( out string path , bool quick = true )
{
string beatmapPath = TestResources . GetTestBeatmapForImport ( quick ) ;
path = beatmapPath ;
return new InvokeOnDisposal ( ( ) = > File . Delete ( beatmapPath ) ) ;
}
2022-07-26 13:53:20 +08:00
}
}