2021-09-30 23:05:14 +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.
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using NUnit.Framework ;
using osu.Framework.Extensions ;
using osu.Framework.Extensions.ObjectExtensions ;
using osu.Framework.Logging ;
using osu.Game.Beatmaps ;
using osu.Game.Database ;
2021-11-19 15:07:55 +08:00
using osu.Game.Extensions ;
2021-09-30 23:05:14 +08:00
using osu.Game.Models ;
2021-11-12 14:48:38 +08:00
using osu.Game.Overlays.Notifications ;
2021-11-19 18:07:21 +08:00
using osu.Game.Rulesets ;
2023-07-01 01:13:14 +08:00
using osu.Game.Scoring ;
2021-09-30 23:05:14 +08:00
using osu.Game.Tests.Resources ;
using Realms ;
using SharpCompress.Archives ;
using SharpCompress.Archives.Zip ;
using SharpCompress.Common ;
using SharpCompress.Writers.Zip ;
namespace osu.Game.Tests.Database
{
[TestFixture]
public class BeatmapImporterTests : RealmTest
{
2022-01-07 13:17:22 +08:00
[Test]
2022-01-12 11:42:45 +08:00
public void TestDetachBeatmapSet ( )
2022-01-07 13:17:22 +08:00
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2022-01-07 13:17:22 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using ( new RealmRulesetStore ( realm , storage ) )
2022-01-07 13:17:22 +08:00
{
2022-06-20 14:14:57 +08:00
var beatmapSet = await importer . Import ( new ImportTask ( TestResources . GetTestBeatmapStream ( ) , "renatus.osz" ) ) ;
2022-01-07 13:17:22 +08:00
2022-01-12 11:40:09 +08:00
Assert . NotNull ( beatmapSet ) ;
Debug . Assert ( beatmapSet ! = null ) ;
2022-01-07 13:17:22 +08:00
2022-01-12 11:40:09 +08:00
BeatmapSetInfo ? detachedBeatmapSet = null ;
2022-01-07 13:17:22 +08:00
2022-01-12 11:40:09 +08:00
beatmapSet . PerformRead ( live = >
2022-01-07 13:17:22 +08:00
{
2022-01-12 11:40:09 +08:00
detachedBeatmapSet = live . Detach ( ) ;
2022-01-07 13:17:22 +08:00
2022-01-19 00:05:22 +08:00
// files are omitted
Assert . AreEqual ( 0 , detachedBeatmapSet . Files . Count ) ;
2022-01-12 11:40:09 +08:00
Assert . AreEqual ( live . Beatmaps . Count , detachedBeatmapSet . Beatmaps . Count ) ;
Assert . AreEqual ( live . Beatmaps . Select ( f = > f . Difficulty ) . Count ( ) , detachedBeatmapSet . Beatmaps . Select ( f = > f . Difficulty ) . Count ( ) ) ;
Assert . AreEqual ( live . Metadata , detachedBeatmapSet . Metadata ) ;
2022-01-07 13:17:22 +08:00
} ) ;
2022-01-12 11:40:09 +08:00
Debug . Assert ( detachedBeatmapSet ! = null ) ;
2022-01-07 13:17:22 +08:00
2022-01-12 11:40:09 +08:00
// Check detached instances can all be accessed without throwing.
2022-01-19 00:05:22 +08:00
Assert . AreEqual ( 0 , detachedBeatmapSet . Files . Count ) ;
2022-01-12 11:40:09 +08:00
Assert . NotNull ( detachedBeatmapSet . Beatmaps . Count ) ;
Assert . NotZero ( detachedBeatmapSet . Beatmaps . Select ( f = > f . Difficulty ) . Count ( ) ) ;
Assert . NotNull ( detachedBeatmapSet . Metadata ) ;
2022-01-12 11:42:45 +08:00
// Check cyclic reference to beatmap set
Assert . AreEqual ( detachedBeatmapSet , detachedBeatmapSet . Beatmaps . First ( ) . BeatmapSet ) ;
2022-01-07 13:17:22 +08:00
}
} ) ;
}
2022-01-12 11:56:42 +08:00
[Test]
public void TestUpdateDetachedBeatmapSet ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2022-01-12 11:56:42 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using ( new RealmRulesetStore ( realm , storage ) )
2022-01-12 11:56:42 +08:00
{
2022-06-20 14:14:57 +08:00
var beatmapSet = await importer . Import ( new ImportTask ( TestResources . GetTestBeatmapStream ( ) , "renatus.osz" ) ) ;
2022-01-12 11:56:42 +08:00
Assert . NotNull ( beatmapSet ) ;
Debug . Assert ( beatmapSet ! = null ) ;
2022-01-19 00:05:22 +08:00
// Detach at the BeatmapInfo point, similar to what GetWorkingBeatmap does.
BeatmapInfo ? detachedBeatmap = null ;
beatmapSet . PerformRead ( s = > detachedBeatmap = s . Beatmaps . First ( ) . Detach ( ) ) ;
2022-01-12 11:56:42 +08:00
2022-01-19 00:05:22 +08:00
BeatmapSetInfo ? detachedBeatmapSet = detachedBeatmap ? . BeatmapSet ;
2022-01-12 11:56:42 +08:00
Debug . Assert ( detachedBeatmapSet ! = null ) ;
var newUser = new RealmUser { Username = "peppy" , OnlineID = 2 } ;
detachedBeatmapSet . Beatmaps . First ( ) . Metadata . Artist = "New Artist" ;
detachedBeatmapSet . Beatmaps . First ( ) . Metadata . Author = newUser ;
Assert . AreNotEqual ( detachedBeatmapSet . Status , BeatmapOnlineStatus . Ranked ) ;
detachedBeatmapSet . Status = BeatmapOnlineStatus . Ranked ;
2022-01-12 13:38:58 +08:00
beatmapSet . PerformWrite ( s = >
{
detachedBeatmapSet . CopyChangesToRealm ( s ) ;
} ) ;
2022-01-12 11:56:42 +08:00
beatmapSet . PerformRead ( s = >
{
2022-01-12 13:38:58 +08:00
// Check above changes explicitly.
2022-01-12 11:56:42 +08:00
Assert . AreEqual ( BeatmapOnlineStatus . Ranked , s . Status ) ;
Assert . AreEqual ( "New Artist" , s . Beatmaps . First ( ) . Metadata . Artist ) ;
Assert . AreEqual ( newUser , s . Beatmaps . First ( ) . Metadata . Author ) ;
2022-01-12 13:38:58 +08:00
Assert . NotZero ( s . Files . Count ) ;
// Check nothing was lost in the copy operation.
Assert . AreEqual ( s . Files . Count , detachedBeatmapSet . Files . Count ) ;
Assert . AreEqual ( s . Files . Select ( f = > f . File ) . Count ( ) , detachedBeatmapSet . Files . Select ( f = > f . File ) . Count ( ) ) ;
Assert . AreEqual ( s . Beatmaps . Count , detachedBeatmapSet . Beatmaps . Count ) ;
Assert . AreEqual ( s . Beatmaps . Select ( f = > f . Difficulty ) . Count ( ) , detachedBeatmapSet . Beatmaps . Select ( f = > f . Difficulty ) . Count ( ) ) ;
Assert . AreEqual ( s . Metadata , detachedBeatmapSet . Metadata ) ;
2022-01-12 11:56:42 +08:00
} ) ;
}
} ) ;
}
2022-05-10 20:03:55 +08:00
[Test]
public void TestAddFileToAsyncImportedBeatmap ( )
{
RunTestWithRealm ( ( realm , storage ) = >
{
BeatmapSetInfo ? detachedSet = null ;
2022-06-16 17:53:13 +08:00
var manager = new ModelManager < BeatmapSetInfo > ( storage , realm ) ;
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-05-10 20:03:55 +08:00
using ( new RealmRulesetStore ( realm , storage ) )
{
Task . Run ( async ( ) = >
{
2022-06-20 14:14:57 +08:00
var beatmapSet = await importer . Import ( new ImportTask ( TestResources . GetTestBeatmapStream ( ) , "renatus.osz" ) ) ;
2022-05-10 20:03:55 +08:00
Assert . NotNull ( beatmapSet ) ;
Debug . Assert ( beatmapSet ! = null ) ;
// Intentionally detach on async thread as to not trigger a refresh on the main thread.
beatmapSet . PerformRead ( s = > detachedSet = s . Detach ( ) ) ;
} ) . WaitSafely ( ) ;
Debug . Assert ( detachedSet ! = null ) ;
2022-06-16 17:53:13 +08:00
manager . AddFile ( detachedSet , new MemoryStream ( ) , "test" ) ;
2022-05-10 20:03:55 +08:00
}
} ) ;
}
2021-09-30 23:05:14 +08:00
[Test]
public void TestImportBeatmapThenCleanup ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using ( new RealmRulesetStore ( realm , storage ) )
2021-09-30 23:05:14 +08:00
{
2022-06-20 14:14:57 +08:00
var imported = await importer . Import ( new ImportTask ( TestResources . GetTestBeatmapStream ( ) , "renatus.osz" ) ) ;
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
Assert . AreEqual ( 1 , realm . Realm . All < BeatmapSetInfo > ( ) . Count ( ) ) ;
2021-09-30 23:05:14 +08:00
Assert . NotNull ( imported ) ;
Debug . Assert ( imported ! = null ) ;
imported . PerformWrite ( s = > s . DeletePending = true ) ;
2022-01-24 18:59:58 +08:00
Assert . AreEqual ( 1 , realm . Realm . All < BeatmapSetInfo > ( ) . Count ( s = > s . DeletePending ) ) ;
2021-09-30 23:05:14 +08:00
}
} ) ;
Logger . Log ( "Running with no work to purge pending deletions" ) ;
2022-01-24 18:59:58 +08:00
RunTestWithRealm ( ( realm , _ ) = > { Assert . AreEqual ( 0 , realm . Realm . All < BeatmapSetInfo > ( ) . Count ( ) ) ; } ) ;
2021-09-30 23:05:14 +08:00
}
[Test]
public void TestImportWhenClosed ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
2021-11-22 14:30:11 +08:00
[Test]
public void TestAccessFileAfterImport ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-11-22 14:30:11 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-11-22 14:30:11 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-11-22 14:30:11 +08:00
var beatmap = imported . Beatmaps . First ( ) ;
var file = beatmap . File ;
Assert . NotNull ( file ) ;
Assert . AreEqual ( beatmap . Hash , file ! . File . Hash ) ;
} ) ;
}
2021-09-30 23:05:14 +08:00
[Test]
public void TestImportThenDelete ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
deleteBeatmapSet ( imported , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
[Test]
public void TestImportThenDeleteFromStream ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? tempPath = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
2022-01-26 12:37:33 +08:00
Live < BeatmapSetInfo > ? importedSet ;
2021-09-30 23:05:14 +08:00
using ( var stream = File . OpenRead ( tempPath ) )
{
importedSet = await importer . Import ( new ImportTask ( stream , Path . GetFileName ( tempPath ) ) ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
}
Assert . NotNull ( importedSet ) ;
Debug . Assert ( importedSet ! = null ) ;
Assert . IsTrue ( File . Exists ( tempPath ) , "Stream source file somehow went missing" ) ;
File . Delete ( tempPath ) ;
2022-01-24 18:59:58 +08:00
var imported = realm . Realm . All < BeatmapSetInfo > ( ) . First ( beatmapSet = > beatmapSet . ID = = importedSet . ID ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
deleteBeatmapSet ( imported , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
[Test]
public void TestImportThenImport ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
var importedSecondTime = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert . IsTrue ( imported . ID = = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID = = importedSecondTime . Beatmaps . First ( ) . ID ) ;
2022-01-24 18:59:58 +08:00
checkBeatmapSetCount ( realm . Realm , 1 ) ;
checkSingleReferencedFileCount ( realm . Realm , 18 ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
2022-06-20 15:06:22 +08:00
[Test]
public void TestImportDirectoryWithEmptyOsuFiles ( )
{
RunTestWithRealmAsync ( async ( realm , storage ) = >
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-06-20 15:06:22 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
string extractedFolder = $"{temp}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( extractedFolder ) ;
foreach ( var file in new DirectoryInfo ( extractedFolder ) . GetFiles ( "*.osu" ) )
{
using ( file . Open ( FileMode . Create ) )
{
// empty file.
}
}
var imported = await importer . Import ( new ImportTask ( extractedFolder ) ) ;
Assert . IsNull ( imported ) ;
}
finally
{
2022-07-26 14:05:01 +08:00
File . Delete ( temp ) ;
2022-06-20 15:06:22 +08:00
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
2021-09-30 23:05:14 +08:00
[Test]
public void TestImportThenImportWithReZip ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
string hashBefore = hashFile ( temp ) ;
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( extractedFolder ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
// zip files differ because different compression or encoder.
Assert . AreNotEqual ( hashBefore , hashFile ( temp ) ) ;
var importedSecondTime = await importer . Import ( new ImportTask ( temp ) ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
Assert . NotNull ( importedSecondTime ) ;
Debug . Assert ( importedSecondTime ! = null ) ;
// but contents doesn't, so existing should still be used.
Assert . IsTrue ( imported . ID = = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID = = importedSecondTime . PerformRead ( s = > s . Beatmaps . First ( ) . ID ) ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
[Test]
public void TestImportThenImportWithChangedHashedFile ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
await createScoreForBeatmap ( realm . Realm , imported . Beatmaps . First ( ) ) ;
2021-09-30 23:05:14 +08:00
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( extractedFolder ) ;
// arbitrary write to hashed file
// this triggers the special BeatmapManager.PreImport deletion/replacement flow.
using ( var sw = new FileInfo ( Directory . GetFiles ( extractedFolder , "*.osu" ) . First ( ) ) . AppendText ( ) )
await sw . WriteLineAsync ( "// changed" ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
var importedSecondTime = await importer . Import ( new ImportTask ( temp ) ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
// check the newly "imported" beatmap is not the original.
Assert . NotNull ( importedSecondTime ) ;
Debug . Assert ( importedSecondTime ! = null ) ;
Assert . IsTrue ( imported . ID ! = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID ! = importedSecondTime . PerformRead ( s = > s . Beatmaps . First ( ) . ID ) ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
2023-07-01 01:13:14 +08:00
[Test]
2023-07-01 01:15:38 +08:00
public void TestImport_ThenModifyMapWithScore_ThenImport ( )
2023-07-01 01:13:14 +08:00
{
RunTestWithRealmAsync ( async ( realm , storage ) = >
{
var importer = new BeatmapImporter ( storage , realm ) ;
2023-07-01 14:48:42 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2023-07-01 01:13:14 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
await createScoreForBeatmap ( realm . Realm , imported . Beatmaps . First ( ) ) ;
2023-07-02 02:37:33 +08:00
// imitate making local changes via editor
2023-07-02 02:42:34 +08:00
// ReSharper disable once MethodHasAsyncOverload
realm . Write ( _ = >
2023-07-01 01:13:14 +08:00
{
2023-07-02 02:42:34 +08:00
BeatmapInfo beatmap = imported . Beatmaps . First ( ) ;
beatmap . Hash = "new_hash" ;
beatmap . ResetOnlineInfo ( ) ;
2023-07-01 01:13:14 +08:00
} ) ;
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 (see: https://github.com/ppy/osu/pull/22539).
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
2023-07-01 01:13:14 +08:00
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 ) ;
} ) ;
}
2021-09-30 23:05:14 +08:00
[Test]
public void TestImportThenImportWithChangedFile ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( extractedFolder ) ;
// arbitrary write to non-hashed file
using ( var sw = new FileInfo ( Directory . GetFiles ( extractedFolder , "*.mp3" ) . First ( ) ) . AppendText ( ) )
await sw . WriteLineAsync ( "text" ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
var importedSecondTime = await importer . Import ( new ImportTask ( temp ) ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
Assert . NotNull ( importedSecondTime ) ;
Debug . Assert ( importedSecondTime ! = null ) ;
// check the newly "imported" beatmap is not the original.
Assert . IsTrue ( imported . ID ! = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID ! = importedSecondTime . PerformRead ( s = > s . Beatmaps . First ( ) . ID ) ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
[Test]
public void TestImportThenImportWithDifferentFilename ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( extractedFolder ) ;
// change filename
var firstFile = new FileInfo ( Directory . GetFiles ( extractedFolder ) . First ( ) ) ;
firstFile . MoveTo ( Path . Combine ( firstFile . DirectoryName . AsNonNull ( ) , $"{firstFile.Name}-changed{firstFile.Extension}" ) ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
var importedSecondTime = await importer . Import ( new ImportTask ( temp ) ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
Assert . NotNull ( importedSecondTime ) ;
Debug . Assert ( importedSecondTime ! = null ) ;
// check the newly "imported" beatmap is not the original.
Assert . IsTrue ( imported . ID ! = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID ! = importedSecondTime . PerformRead ( s = > s . Beatmaps . First ( ) . ID ) ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
[Test]
public void TestImportCorruptThenImport ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
var firstFile = imported . Files . First ( ) ;
2022-06-14 23:48:00 +08:00
var fileStorage = storage . GetStorageForDirectory ( "files" ) ;
2021-09-30 23:05:14 +08:00
long originalLength ;
2022-06-14 23:48:00 +08:00
using ( var stream = fileStorage . GetStream ( firstFile . File . GetStoragePath ( ) ) )
2021-09-30 23:05:14 +08:00
originalLength = stream . Length ;
2022-06-14 23:48:00 +08:00
using ( var stream = fileStorage . CreateFileSafely ( firstFile . File . GetStoragePath ( ) ) )
2021-09-30 23:05:14 +08:00
stream . WriteByte ( 0 ) ;
2022-01-24 18:59:58 +08:00
var importedSecondTime = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-06-14 23:48:00 +08:00
using ( var stream = fileStorage . GetStream ( firstFile . File . GetStoragePath ( ) ) )
2021-09-30 23:05:14 +08:00
Assert . AreEqual ( stream . Length , originalLength , "Corruption was not fixed on second import" ) ;
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert . IsTrue ( imported . ID = = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID = = importedSecondTime . Beatmaps . First ( ) . ID ) ;
2022-01-24 18:59:58 +08:00
checkBeatmapSetCount ( realm . Realm , 1 ) ;
checkSingleReferencedFileCount ( realm . Realm , 18 ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
2021-11-12 14:48:38 +08:00
[Test]
public void TestModelCreationFailureDoesntReturn ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-11-12 14:48:38 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-11-12 14:48:38 +08:00
var progressNotification = new ImportProgressNotification ( ) ;
var zipStream = new MemoryStream ( ) ;
using ( var zip = ZipArchive . Create ( ) )
zip . SaveTo ( zipStream , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
var imported = await importer . Import (
progressNotification ,
2022-12-13 20:03:25 +08:00
new [ ] { new ImportTask ( zipStream , string . Empty ) }
2021-11-12 14:48:38 +08:00
) ;
2022-03-24 11:53:50 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-01-24 18:59:58 +08:00
checkBeatmapSetCount ( realm . Realm , 0 ) ;
checkBeatmapCount ( realm . Realm , 0 ) ;
2021-11-12 14:48:38 +08:00
Assert . IsEmpty ( imported ) ;
Assert . AreEqual ( ProgressNotificationState . Cancelled , progressNotification . State ) ;
} ) ;
}
2021-09-30 23:05:14 +08:00
[Test]
public void TestRollbackOnFailure ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
int loggedExceptionCount = 0 ;
Logger . NewEntry + = l = >
{
if ( l . Target = = LoggingTarget . Database & & l . Exception ! = null )
Interlocked . Increment ( ref loggedExceptionCount ) ;
} ;
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
realm . Realm . Write ( ( ) = > imported . Hash + = "-changed" ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
checkBeatmapSetCount ( realm . Realm , 1 ) ;
checkBeatmapCount ( realm . Realm , 12 ) ;
checkSingleReferencedFileCount ( realm . Realm , 18 ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? brokenTempFilename = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
MemoryStream brokenOsu = new MemoryStream ( ) ;
MemoryStream brokenOsz = new MemoryStream ( await File . ReadAllBytesAsync ( brokenTempFilename ) ) ;
File . Delete ( brokenTempFilename ) ;
using ( var outStream = File . Open ( brokenTempFilename , FileMode . CreateNew ) )
using ( var zip = ZipArchive . Open ( brokenOsz ) )
{
2022-06-27 16:04:28 +08:00
foreach ( var entry in zip . Entries . ToArray ( ) )
{
if ( entry . Key . EndsWith ( ".osu" , StringComparison . InvariantCulture ) )
zip . RemoveEntry ( entry ) ;
}
2021-09-30 23:05:14 +08:00
zip . AddEntry ( "broken.osu" , brokenOsu , false ) ;
zip . SaveTo ( outStream , CompressionType . Deflate ) ;
}
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
try
{
await importer . Import ( new ImportTask ( brokenTempFilename ) ) ;
}
catch
{
}
2022-03-24 11:53:50 +08:00
EnsureLoaded ( realm . Realm ) ;
2022-01-24 18:59:58 +08:00
checkBeatmapSetCount ( realm . Realm , 1 ) ;
checkBeatmapCount ( realm . Realm , 12 ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
checkSingleReferencedFileCount ( realm . Realm , 18 ) ;
2021-09-30 23:05:14 +08:00
2022-06-27 16:04:28 +08:00
Assert . AreEqual ( 0 , loggedExceptionCount ) ;
2021-09-30 23:05:14 +08:00
File . Delete ( brokenTempFilename ) ;
} ) ;
}
[Test]
2021-11-29 13:48:28 +08:00
public void TestImportThenDeleteThenImportOptimisedPath ( )
2021-09-30 23:05:14 +08:00
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-06-14 23:27:09 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm , batchImport : true ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
deleteBeatmapSet ( imported , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2021-11-29 13:48:28 +08:00
Assert . IsTrue ( imported . DeletePending ) ;
2022-03-22 13:10:21 +08:00
var originalAddedDate = imported . DateAdded ;
2022-01-24 18:59:58 +08:00
var importedSecondTime = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-11-29 13:48:28 +08:00
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert . IsTrue ( imported . ID = = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID = = importedSecondTime . Beatmaps . First ( ) . ID ) ;
Assert . IsFalse ( imported . DeletePending ) ;
Assert . IsFalse ( importedSecondTime . DeletePending ) ;
2022-03-22 13:10:21 +08:00
Assert . That ( importedSecondTime . DateAdded , Is . GreaterThan ( originalAddedDate ) ) ;
2021-11-29 13:48:28 +08:00
} ) ;
}
2022-07-25 16:18:28 +08:00
[Test]
public void TestImportThenReimportWithNewDifficulty ( )
{
RunTestWithRealmAsync ( async ( realm , storage ) = >
{
var importer = new BeatmapImporter ( storage , realm ) ;
using var store = new RealmRulesetStore ( realm , storage ) ;
string? pathOriginal = TestResources . GetTestBeatmapForImport ( ) ;
string pathMissingOneBeatmap = pathOriginal . Replace ( ".osz" , "_missing_difficulty.osz" ) ;
string extractedFolder = $"{pathOriginal}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
using ( var zip = ZipArchive . Open ( pathOriginal ) )
zip . WriteToDirectory ( extractedFolder ) ;
// remove one difficulty before first import
new FileInfo ( Directory . GetFiles ( extractedFolder , "*.osu" ) . First ( ) ) . Delete ( ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( pathMissingOneBeatmap , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
var firstImport = await importer . Import ( new ImportTask ( pathMissingOneBeatmap ) ) ;
Assert . That ( firstImport , Is . Not . Null ) ;
2022-08-01 16:06:45 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-25 16:18:28 +08:00
Assert . That ( realm . Realm . All < BeatmapSetInfo > ( ) . Where ( s = > ! s . DeletePending ) , Has . Count . EqualTo ( 1 ) ) ;
Assert . That ( realm . Realm . All < BeatmapSetInfo > ( ) . First ( s = > ! s . DeletePending ) . Beatmaps , Has . Count . EqualTo ( 11 ) ) ;
// Second import matches first but contains one extra .osu file.
var secondImport = await importer . Import ( new ImportTask ( pathOriginal ) ) ;
Assert . That ( secondImport , Is . Not . Null ) ;
2022-08-01 16:06:45 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2022-07-25 16:18:28 +08:00
Assert . That ( realm . Realm . All < BeatmapInfo > ( ) , Has . Count . EqualTo ( 23 ) ) ;
Assert . That ( realm . Realm . All < BeatmapSetInfo > ( ) , Has . Count . EqualTo ( 2 ) ) ;
Assert . That ( realm . Realm . All < BeatmapSetInfo > ( ) . Where ( s = > ! s . DeletePending ) , Has . Count . EqualTo ( 1 ) ) ;
Assert . That ( realm . Realm . All < BeatmapSetInfo > ( ) . First ( s = > ! s . DeletePending ) . Beatmaps , Has . Count . EqualTo ( 12 ) ) ;
// check the newly "imported" beatmap is not the original.
Assert . That ( firstImport ? . ID , Is . Not . EqualTo ( secondImport ? . ID ) ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
2022-01-25 12:42:41 +08:00
[Test]
public void TestImportThenReimportAfterMissingFiles ( )
{
RunTestWithRealmAsync ( async ( realmFactory , storage ) = >
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realmFactory ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realmFactory , storage ) ;
2022-01-25 12:42:41 +08:00
2022-01-25 14:29:45 +08:00
var imported = await LoadOszIntoStore ( importer , realmFactory . Realm ) ;
2022-01-25 12:42:41 +08:00
2022-01-25 14:29:45 +08:00
deleteBeatmapSet ( imported , realmFactory . Realm ) ;
2022-01-25 12:42:41 +08:00
Assert . IsTrue ( imported . DeletePending ) ;
// intentionally nuke all files
storage . DeleteDirectory ( "files" ) ;
Assert . That ( imported . Files . All ( f = > ! storage . GetStorageForDirectory ( "files" ) . Exists ( f . File . GetStoragePath ( ) ) ) ) ;
2022-01-25 14:29:45 +08:00
var importedSecondTime = await LoadOszIntoStore ( importer , realmFactory . Realm ) ;
2022-01-25 12:42:41 +08:00
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert . IsTrue ( imported . ID = = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID = = importedSecondTime . Beatmaps . First ( ) . ID ) ;
Assert . IsFalse ( imported . DeletePending ) ;
Assert . IsFalse ( importedSecondTime . DeletePending ) ;
// check that the files now exist, even though they were deleted above.
Assert . That ( importedSecondTime . Files . All ( f = > storage . GetStorageForDirectory ( "files" ) . Exists ( f . File . GetStoragePath ( ) ) ) ) ;
} ) ;
}
2021-11-29 13:48:28 +08:00
[Test]
public void TestImportThenDeleteThenImportNonOptimisedPath ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-11-29 13:48:28 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-11-29 13:48:28 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-11-29 13:48:28 +08:00
2022-01-24 18:59:58 +08:00
deleteBeatmapSet ( imported , realm . Realm ) ;
2021-11-29 13:48:28 +08:00
Assert . IsTrue ( imported . DeletePending ) ;
2022-03-22 13:10:21 +08:00
var originalAddedDate = imported . DateAdded ;
2022-01-24 18:59:58 +08:00
var importedSecondTime = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert . IsTrue ( imported . ID = = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID = = importedSecondTime . Beatmaps . First ( ) . ID ) ;
2021-11-29 13:48:28 +08:00
Assert . IsFalse ( imported . DeletePending ) ;
Assert . IsFalse ( importedSecondTime . DeletePending ) ;
2022-03-22 13:10:21 +08:00
Assert . That ( importedSecondTime . DateAdded , Is . GreaterThan ( originalAddedDate ) ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
[Test]
public void TestImportThenDeleteThenImportWithOnlineIDsMissing ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
var imported = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-06-03 13:01:04 +08:00
await realm . Realm . WriteAsync ( ( ) = >
2021-09-30 23:05:14 +08:00
{
foreach ( var b in imported . Beatmaps )
2022-07-25 17:51:19 +08:00
b . ResetOnlineInfo ( ) ;
2021-09-30 23:05:14 +08:00
} ) ;
2022-01-24 18:59:58 +08:00
deleteBeatmapSet ( imported , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
var importedSecondTime = await LoadOszIntoStore ( importer , realm . Realm ) ;
2021-09-30 23:05:14 +08:00
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert . IsTrue ( imported . ID ! = importedSecondTime . ID ) ;
Assert . IsTrue ( imported . Beatmaps . First ( ) . ID ! = importedSecondTime . Beatmaps . First ( ) . ID ) ;
} ) ;
}
[Test]
public void TestImportWithDuplicateBeatmapIDs ( )
{
2022-01-25 14:23:51 +08:00
RunTestWithRealm ( ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-11-19 18:07:21 +08:00
var metadata = new BeatmapMetadata
2021-09-30 23:05:14 +08:00
{
Artist = "SomeArtist" ,
2021-11-04 17:22:21 +08:00
Author =
{
Username = "SomeAuthor"
}
2021-09-30 23:05:14 +08:00
} ;
2022-01-24 18:59:58 +08:00
var ruleset = realm . Realm . All < RulesetInfo > ( ) . First ( ) ;
2021-09-30 23:05:14 +08:00
2021-11-19 18:07:21 +08:00
var toImport = new BeatmapSetInfo
2021-09-30 23:05:14 +08:00
{
OnlineID = 1 ,
Beatmaps =
{
2021-11-19 18:07:21 +08:00
new BeatmapInfo ( ruleset , new BeatmapDifficulty ( ) , metadata )
2021-09-30 23:05:14 +08:00
{
OnlineID = 2 ,
} ,
2021-11-19 18:07:21 +08:00
new BeatmapInfo ( ruleset , new BeatmapDifficulty ( ) , metadata )
2021-09-30 23:05:14 +08:00
{
OnlineID = 2 ,
2021-11-24 17:42:47 +08:00
Status = BeatmapOnlineStatus . Loved ,
2021-09-30 23:05:14 +08:00
}
}
} ;
2022-06-20 14:18:07 +08:00
var imported = importer . ImportModel ( toImport ) ;
2021-09-30 23:05:14 +08:00
2022-03-24 11:53:50 +08:00
realm . Run ( r = > r . Refresh ( ) ) ;
2021-09-30 23:05:14 +08:00
Assert . NotNull ( imported ) ;
Debug . Assert ( imported ! = null ) ;
2021-10-18 15:10:37 +08:00
Assert . AreEqual ( - 1 , imported . PerformRead ( s = > s . Beatmaps [ 0 ] . OnlineID ) ) ;
Assert . AreEqual ( - 1 , imported . PerformRead ( s = > s . Beatmaps [ 1 ] . OnlineID ) ) ;
2021-09-30 23:05:14 +08:00
} ) ;
}
[Test]
public void TestImportWhenFileOpen ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
using ( File . OpenRead ( temp ) )
await importer . Import ( temp ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
File . Delete ( temp ) ;
Assert . IsFalse ( File . Exists ( temp ) , "We likely held a read lock on the file when we shouldn't" ) ;
} ) ;
}
[Test]
public void TestImportWithDuplicateHashes ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
Directory . CreateDirectory ( extractedFolder ) ;
try
{
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( extractedFolder ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . AddEntry ( "duplicate.osu" , Directory . GetFiles ( extractedFolder , "*.osu" ) . First ( ) ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
await importer . Import ( temp ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
[Test]
public void TestImportNestedStructure ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
string subfolder = Path . Combine ( extractedFolder , "subfolder" ) ;
Directory . CreateDirectory ( subfolder ) ;
try
{
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( subfolder ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
var imported = await importer . Import ( new ImportTask ( temp ) ) ;
Assert . NotNull ( imported ) ;
Debug . Assert ( imported ! = null ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
Assert . IsFalse ( imported . PerformRead ( s = > s . Files . Any ( f = > f . Filename . Contains ( "subfolder" ) ) ) , "Files contain common subfolder" ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
[Test]
public void TestImportWithIgnoredDirectoryInArchive ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
string extractedFolder = $"{temp}_extracted" ;
string dataFolder = Path . Combine ( extractedFolder , "actual_data" ) ;
string resourceForkFolder = Path . Combine ( extractedFolder , "__MACOSX" ) ;
string resourceForkFilePath = Path . Combine ( resourceForkFolder , ".extracted" ) ;
Directory . CreateDirectory ( dataFolder ) ;
Directory . CreateDirectory ( resourceForkFolder ) ;
using ( var resourceForkFile = File . CreateText ( resourceForkFilePath ) )
{
await resourceForkFile . WriteLineAsync ( "adding content so that it's not empty" ) ;
}
try
{
using ( var zip = ZipArchive . Open ( temp ) )
zip . WriteToDirectory ( dataFolder ) ;
using ( var zip = ZipArchive . Create ( ) )
{
zip . AddAllFromDirectory ( extractedFolder ) ;
zip . SaveTo ( temp , new ZipWriterOptions ( CompressionType . Deflate ) ) ;
}
var imported = await importer . Import ( new ImportTask ( temp ) ) ;
Assert . NotNull ( imported ) ;
Debug . Assert ( imported ! = null ) ;
2022-01-24 18:59:58 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
Assert . IsFalse ( imported . PerformRead ( s = > s . Files . Any ( f = > f . Filename . Contains ( "__MACOSX" ) ) ) , "Files contain resource fork folder, which should be ignored" ) ;
Assert . IsFalse ( imported . PerformRead ( s = > s . Files . Any ( f = > f . Filename . Contains ( "actual_data" ) ) ) , "Files contain common subfolder" ) ;
}
finally
{
Directory . Delete ( extractedFolder , true ) ;
}
} ) ;
}
[Test]
public void TestUpdateBeatmapInfo ( )
{
2022-01-24 18:59:58 +08:00
RunTestWithRealmAsync ( async ( realm , storage ) = >
2021-09-30 23:05:14 +08:00
{
2022-06-20 17:39:53 +08:00
var importer = new BeatmapImporter ( storage , realm ) ;
2022-02-16 16:13:51 +08:00
using var store = new RealmRulesetStore ( realm , storage ) ;
2021-09-30 23:05:14 +08:00
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
await importer . Import ( temp ) ;
2022-03-24 11:53:50 +08:00
EnsureLoaded ( realm . Realm ) ;
2021-09-30 23:05:14 +08:00
// Update via the beatmap, not the beatmap info, to ensure correct linking
2022-01-24 18:59:58 +08:00
BeatmapSetInfo setToUpdate = realm . Realm . All < BeatmapSetInfo > ( ) . First ( ) ;
2021-09-30 23:05:14 +08:00
var beatmapToUpdate = setToUpdate . Beatmaps . First ( ) ;
2022-01-24 18:59:58 +08:00
realm . Realm . Write ( ( ) = > beatmapToUpdate . DifficultyName = "updated" ) ;
2021-09-30 23:05:14 +08:00
2022-01-24 18:59:58 +08:00
BeatmapInfo updatedInfo = realm . Realm . All < BeatmapInfo > ( ) . First ( b = > b . ID = = beatmapToUpdate . ID ) ;
2021-09-30 23:05:14 +08:00
Assert . That ( updatedInfo . DifficultyName , Is . EqualTo ( "updated" ) ) ;
} ) ;
}
2021-11-19 18:07:21 +08:00
public static async Task < BeatmapSetInfo ? > LoadQuickOszIntoOsu ( BeatmapImporter importer , Realm realm )
2021-09-30 23:05:14 +08:00
{
2021-10-27 12:04:41 +08:00
string? temp = TestResources . GetQuickTestBeatmapForImport ( ) ;
2021-09-30 23:05:14 +08:00
var importedSet = await importer . Import ( new ImportTask ( temp ) ) ;
Assert . NotNull ( importedSet ) ;
2021-12-17 17:26:12 +08:00
EnsureLoaded ( realm ) ;
2021-09-30 23:05:14 +08:00
waitForOrAssert ( ( ) = > ! File . Exists ( temp ) , "Temporary file still exists after standard import" , 5000 ) ;
2021-11-19 18:07:21 +08:00
return realm . All < BeatmapSetInfo > ( ) . FirstOrDefault ( beatmapSet = > beatmapSet . ID = = importedSet ! . ID ) ;
2021-09-30 23:05:14 +08:00
}
2022-06-14 23:27:09 +08:00
public static async Task < BeatmapSetInfo > LoadOszIntoStore ( BeatmapImporter importer , Realm realm , string? path = null , bool virtualTrack = false , bool batchImport = false )
2021-09-30 23:05:14 +08:00
{
2021-10-27 12:04:41 +08:00
string? temp = path ? ? TestResources . GetTestBeatmapForImport ( virtualTrack ) ;
2021-09-30 23:05:14 +08:00
2022-12-12 22:56:11 +08:00
var importedSet = await importer . Import ( new ImportTask ( temp ) , new ImportParameters { Batch = batchImport } ) ;
2021-09-30 23:05:14 +08:00
Assert . NotNull ( importedSet ) ;
Debug . Assert ( importedSet ! = null ) ;
2021-12-17 17:26:12 +08:00
EnsureLoaded ( realm ) ;
2021-09-30 23:05:14 +08:00
waitForOrAssert ( ( ) = > ! File . Exists ( temp ) , "Temporary file still exists after standard import" , 5000 ) ;
2021-11-19 18:07:21 +08:00
return realm . All < BeatmapSetInfo > ( ) . First ( beatmapSet = > beatmapSet . ID = = importedSet . ID ) ;
2021-09-30 23:05:14 +08:00
}
2021-11-19 18:07:21 +08:00
private void deleteBeatmapSet ( BeatmapSetInfo imported , Realm realm )
2021-09-30 23:05:14 +08:00
{
realm . Write ( ( ) = > imported . DeletePending = true ) ;
checkBeatmapSetCount ( realm , 0 ) ;
checkBeatmapSetCount ( realm , 1 , true ) ;
2021-11-19 18:07:21 +08:00
Assert . IsTrue ( realm . All < BeatmapSetInfo > ( ) . First ( _ = > true ) . DeletePending ) ;
2021-09-30 23:05:14 +08:00
}
2023-07-01 01:13:14 +08:00
private static Task createScoreForBeatmap ( Realm realm , BeatmapInfo beatmap ) = >
realm . WriteAsync ( ( ) = >
{
realm . Add ( new ScoreInfo
{
OnlineID = 2 ,
BeatmapInfo = beatmap ,
BeatmapHash = beatmap . Hash
} ) ;
} ) ;
2021-09-30 23:05:14 +08:00
private static void checkBeatmapSetCount ( Realm realm , int expected , bool includeDeletePending = false )
{
Assert . AreEqual ( expected , includeDeletePending
2021-11-19 18:07:21 +08:00
? realm . All < BeatmapSetInfo > ( ) . Count ( )
: realm . All < BeatmapSetInfo > ( ) . Count ( s = > ! s . DeletePending ) ) ;
2021-09-30 23:05:14 +08:00
}
private static string hashFile ( string filename )
{
using ( var s = File . OpenRead ( filename ) )
return s . ComputeMD5Hash ( ) ;
}
private static void checkBeatmapCount ( Realm realm , int expected )
{
2021-11-19 18:07:21 +08:00
Assert . AreEqual ( expected , realm . All < BeatmapInfo > ( ) . Where ( _ = > true ) . ToList ( ) . Count ) ;
2021-09-30 23:05:14 +08:00
}
private static void checkSingleReferencedFileCount ( Realm realm , int expected )
{
int singleReferencedCount = 0 ;
foreach ( var f in realm . All < RealmFile > ( ) )
{
if ( f . BacklinksCount = = 1 )
singleReferencedCount + + ;
}
Assert . AreEqual ( expected , singleReferencedCount ) ;
}
2021-12-17 17:26:12 +08:00
internal static void EnsureLoaded ( Realm realm , int timeout = 60000 )
2021-09-30 23:05:14 +08:00
{
2021-11-19 18:07:21 +08:00
IQueryable < BeatmapSetInfo > ? resultSets = null ;
2021-09-30 23:05:14 +08:00
2021-11-30 13:12:49 +08:00
waitForOrAssert ( ( ) = >
2021-11-19 18:07:21 +08:00
{
realm . Refresh ( ) ;
return ( resultSets = realm . All < BeatmapSetInfo > ( ) . Where ( s = > ! s . DeletePending & & s . OnlineID = = 241526 ) ) . Any ( ) ;
} , @"BeatmapSet did not import to the database in allocated time." , timeout ) ;
2021-09-30 23:05:14 +08:00
// ensure we were stored to beatmap database backing...
Assert . IsTrue ( resultSets ? . Count ( ) = = 1 , $@"Incorrect result count found ({resultSets?.Count()} but should be 1)." ) ;
2021-11-19 18:07:21 +08:00
IEnumerable < BeatmapSetInfo > queryBeatmapSets ( ) = > realm . All < BeatmapSetInfo > ( ) . Where ( s = > ! s . DeletePending & & s . OnlineID = = 241526 ) ;
2021-09-30 23:05:14 +08:00
var set = queryBeatmapSets ( ) . First ( ) ;
// ReSharper disable once PossibleUnintendedReferenceComparison
2021-11-19 18:07:21 +08:00
IEnumerable < BeatmapInfo > queryBeatmaps ( ) = > realm . All < BeatmapInfo > ( ) . Where ( s = > s . BeatmapSet ! = null & & s . BeatmapSet = = set ) ;
2021-09-30 23:05:14 +08:00
2021-11-30 13:12:49 +08:00
Assert . AreEqual ( 12 , queryBeatmaps ( ) . Count ( ) , @"Beatmap count was not correct" ) ;
Assert . AreEqual ( 1 , queryBeatmapSets ( ) . Count ( ) , @"Beatmapset count was not correct" ) ;
2021-09-30 23:05:14 +08:00
2021-11-30 13:12:49 +08:00
int countBeatmapSetBeatmaps ;
int countBeatmaps ;
2021-09-30 23:05:14 +08:00
2021-11-30 13:12:49 +08:00
Assert . AreEqual (
countBeatmapSetBeatmaps = queryBeatmapSets ( ) . First ( ) . Beatmaps . Count ,
countBeatmaps = queryBeatmaps ( ) . Count ( ) ,
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps})." ) ;
2021-09-30 23:05:14 +08:00
2021-11-19 18:07:21 +08:00
foreach ( BeatmapInfo b in set . Beatmaps )
2021-09-30 23:05:14 +08:00
Assert . IsTrue ( set . Beatmaps . Any ( c = > c . OnlineID = = b . OnlineID ) ) ;
Assert . IsTrue ( set . Beatmaps . Count > 0 ) ;
}
private static void waitForOrAssert ( Func < bool > result , string failureMessage , int timeout = 60000 )
{
const int sleep = 200 ;
while ( timeout > 0 )
{
Thread . Sleep ( sleep ) ;
timeout - = sleep ;
if ( result ( ) )
return ;
}
Assert . Fail ( failureMessage ) ;
}
}
}