mirror of
https://github.com/osukey/osukey.git
synced 2025-06-05 12:57:39 +09:00
Move ArchiveModelManager import process to async flow
This commit is contained in:
parent
3182f88ea8
commit
f090e292c9
@ -21,14 +21,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public class ImportBeatmapTest
|
public class ImportBeatmapTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportWhenClosed()
|
public async Task TestImportWhenClosed()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LoadOszIntoOsu(loadOsu(host));
|
await LoadOszIntoOsu(loadOsu(host));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDelete()
|
public async Task TestImportThenDelete()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
|
||||||
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
deleteBeatmapSet(imported, osu);
|
deleteBeatmapSet(imported, osu);
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImport()
|
public async Task TestImportThenImport()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
|
||||||
@ -68,8 +68,8 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// 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.ID == importedSecondTime.ID);
|
||||||
@ -88,7 +88,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRollbackOnFailure()
|
public async Task TestRollbackOnFailure()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
|
||||||
@ -104,7 +104,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
manager.ItemAdded += (_, __) => fireCount++;
|
manager.ItemAdded += (_, __) => fireCount++;
|
||||||
manager.ItemRemoved += _ => fireCount++;
|
manager.ItemRemoved += _ => fireCount++;
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
Assert.AreEqual(0, fireCount -= 1);
|
Assert.AreEqual(0, fireCount -= 1);
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
||||||
|
|
||||||
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
||||||
manager.Import(breakTemp);
|
await manager.Import(breakTemp);
|
||||||
|
|
||||||
// no events should be fired in the case of a rollback.
|
// no events should be fired in the case of a rollback.
|
||||||
Assert.AreEqual(0, fireCount);
|
Assert.AreEqual(0, fireCount);
|
||||||
@ -149,7 +149,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImportDifferentHash()
|
public async Task TestImportThenImportDifferentHash()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
|
||||||
@ -159,12 +159,12 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
imported.Hash += "-changed";
|
imported.Hash += "-changed";
|
||||||
manager.Update(imported);
|
manager.Update(imported);
|
||||||
|
|
||||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||||
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
|
||||||
@ -181,7 +181,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDeleteThenImport()
|
public async Task TestImportThenDeleteThenImport()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
|
||||||
@ -190,11 +190,11 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
deleteBeatmapSet(imported, osu);
|
deleteBeatmapSet(imported, osu);
|
||||||
|
|
||||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// 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.ID == importedSecondTime.ID);
|
||||||
@ -209,7 +209,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
|
||||||
@ -218,7 +218,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
if (set)
|
if (set)
|
||||||
imported.OnlineBeatmapSetID = 1234;
|
imported.OnlineBeatmapSetID = 1234;
|
||||||
@ -229,7 +229,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
deleteBeatmapSet(imported, osu);
|
deleteBeatmapSet(imported, osu);
|
||||||
|
|
||||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
|
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
|
||||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||||
@ -243,7 +243,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportWithDuplicateBeatmapIDs()
|
public async Task TestImportWithDuplicateBeatmapIDs()
|
||||||
{
|
{
|
||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
|
||||||
@ -284,7 +284,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
var imported = manager.Import(toImport);
|
var imported = await manager.Import(toImport);
|
||||||
|
|
||||||
Assert.NotNull(imported);
|
Assert.NotNull(imported);
|
||||||
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
|
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
|
||||||
@ -330,7 +330,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportWhenFileOpen()
|
public async Task TestImportWhenFileOpen()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
|
||||||
{
|
{
|
||||||
@ -339,7 +339,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
var temp = TestResources.GetTestBeatmapForImport();
|
var temp = TestResources.GetTestBeatmapForImport();
|
||||||
using (File.OpenRead(temp))
|
using (File.OpenRead(temp))
|
||||||
osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||||
ensureLoaded(osu);
|
ensureLoaded(osu);
|
||||||
File.Delete(temp);
|
File.Delete(temp);
|
||||||
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
||||||
@ -351,13 +351,13 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
|
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null)
|
||||||
{
|
{
|
||||||
var temp = path ?? TestResources.GetTestBeatmapForImport();
|
var temp = path ?? TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
manager.Import(temp);
|
await manager.Import(temp);
|
||||||
|
|
||||||
var imported = manager.GetAllUsableBeatmapSets();
|
var imported = manager.GetAllUsableBeatmapSets();
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
public class ImportScoreTest
|
public class ImportScoreTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicImport()
|
public async Task TestBasicImport()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = await loadOsu(host);
|
||||||
|
|
||||||
var toImport = new ScoreInfo
|
var toImport = new ScoreInfo
|
||||||
{
|
{
|
||||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
OnlineScoreID = 12345,
|
OnlineScoreID = 12345,
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = loadIntoOsu(osu, toImport);
|
var imported = await loadIntoOsu(osu, toImport);
|
||||||
|
|
||||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||||
@ -62,20 +62,20 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportMods()
|
public async Task TestImportMods()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = await loadOsu(host);
|
||||||
|
|
||||||
var toImport = new ScoreInfo
|
var toImport = new ScoreInfo
|
||||||
{
|
{
|
||||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = loadIntoOsu(osu, toImport);
|
var imported = await loadIntoOsu(osu, toImport);
|
||||||
|
|
||||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
|
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
|
||||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
|
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
|
||||||
@ -88,13 +88,13 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportStatistics()
|
public async Task TestImportStatistics()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
var osu = await loadOsu(host);
|
||||||
|
|
||||||
var toImport = new ScoreInfo
|
var toImport = new ScoreInfo
|
||||||
{
|
{
|
||||||
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = loadIntoOsu(osu, toImport);
|
var imported = await loadIntoOsu(osu, toImport);
|
||||||
|
|
||||||
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
|
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
|
||||||
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
|
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
|
||||||
@ -117,7 +117,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
||||||
{
|
{
|
||||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
@ -125,20 +125,24 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
score.Ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||||
scoreManager.Import(score);
|
await scoreManager.Import(score);
|
||||||
|
|
||||||
return scoreManager.GetAllUsableScores().First();
|
return scoreManager.GetAllUsableScores().First();
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuGameBase loadOsu(GameHost host)
|
private async Task<OsuGameBase> loadOsu(GameHost host)
|
||||||
{
|
{
|
||||||
var osu = new OsuGameBase();
|
var osu = new OsuGameBase();
|
||||||
|
|
||||||
|
#pragma warning disable 4014
|
||||||
Task.Run(() => host.Run(osu));
|
Task.Run(() => host.Run(osu));
|
||||||
|
#pragma warning restore 4014
|
||||||
|
|
||||||
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||||
|
|
||||||
var beatmapFile = TestResources.GetTestBeatmapForImport();
|
var beatmapFile = TestResources.GetTestBeatmapForImport();
|
||||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
beatmapManager.Import(beatmapFile);
|
await beatmapManager.Import(beatmapFile);
|
||||||
|
|
||||||
return osu;
|
return osu;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default));
|
||||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||||
|
|
||||||
manager.Import(TestResources.GetTestBeatmapForImport());
|
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||||
|
|
||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
this.api = api;
|
this.api = api;
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
|
|
||||||
testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu);
|
testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -18,4 +18,9 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Microsoft.Win32.Registry, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<HintPath>..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.win32.registry\4.5.0\ref\netstandard2.0\Microsoft.Win32.Registry.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -2,10 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -14,6 +16,7 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
@ -72,6 +75,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
||||||
|
|
||||||
|
private readonly BeatmapUpdateQueue updateQueue;
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
|
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
|
||||||
WorkingBeatmap defaultBeatmap = null)
|
WorkingBeatmap defaultBeatmap = null)
|
||||||
: base(storage, contextFactory, new BeatmapStore(contextFactory), host)
|
: base(storage, contextFactory, new BeatmapStore(contextFactory), host)
|
||||||
@ -86,9 +91,11 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmaps = (BeatmapStore)ModelStore;
|
beatmaps = (BeatmapStore)ModelStore;
|
||||||
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||||
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||||
|
|
||||||
|
updateQueue = new BeatmapUpdateQueue(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive)
|
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
||||||
@ -104,8 +111,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
validateOnlineIds(beatmapSet);
|
validateOnlineIds(beatmapSet);
|
||||||
|
|
||||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
await Task.WhenAll(beatmapSet.Beatmaps.Select(b => updateQueue.Enqueue(new UpdateItem(b, cancellationToken)).Task).ToArray());
|
||||||
fetchAndPopulateOnlineValues(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PreImport(BeatmapSetInfo beatmapSet)
|
protected override void PreImport(BeatmapSetInfo beatmapSet)
|
||||||
@ -181,10 +187,10 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
request.Success += filename =>
|
request.Success += filename =>
|
||||||
{
|
{
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(async () =>
|
||||||
{
|
{
|
||||||
// This gets scheduled back to the update thread, but we want the import to run in the background.
|
// This gets scheduled back to the update thread, but we want the import to run in the background.
|
||||||
Import(downloadNotification, filename);
|
await Import(downloadNotification, filename);
|
||||||
currentDownloads.Remove(request);
|
currentDownloads.Remove(request);
|
||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
};
|
};
|
||||||
@ -381,47 +387,6 @@ namespace osu.Game.Beatmaps
|
|||||||
return beatmapInfos;
|
return beatmapInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="beatmap">The beatmap to populate.</param>
|
|
||||||
/// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param>
|
|
||||||
/// <returns>True if population was successful.</returns>
|
|
||||||
private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false)
|
|
||||||
{
|
|
||||||
if (api?.State != APIState.Online)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null
|
|
||||||
&& beatmap.Status != BeatmapSetOnlineStatus.None && beatmap.BeatmapSet.Status != BeatmapSetOnlineStatus.None)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
Logger.Log("Attempting online lookup for the missing values...", LoggingTarget.Database);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var req = new GetBeatmapRequest(beatmap);
|
|
||||||
|
|
||||||
req.Perform(api);
|
|
||||||
|
|
||||||
var res = req.Result;
|
|
||||||
|
|
||||||
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
|
|
||||||
|
|
||||||
beatmap.Status = res.Status;
|
|
||||||
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
|
||||||
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
|
||||||
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Log($"Failed ({e})", LoggingTarget.Database);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -455,5 +420,111 @@ namespace osu.Game.Beatmaps
|
|||||||
public override bool IsImportant => false;
|
public override bool IsImportant => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BeatmapUpdateQueue
|
||||||
|
{
|
||||||
|
private readonly IAPIProvider api;
|
||||||
|
private readonly Queue<UpdateItem> queue = new Queue<UpdateItem>();
|
||||||
|
|
||||||
|
private int activeThreads;
|
||||||
|
|
||||||
|
public BeatmapUpdateQueue(IAPIProvider api)
|
||||||
|
{
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateItem Enqueue(UpdateItem item)
|
||||||
|
{
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
queue.Enqueue(item);
|
||||||
|
|
||||||
|
if (activeThreads >= 16)
|
||||||
|
return item;
|
||||||
|
|
||||||
|
new Thread(runWork) { IsBackground = true }.Start();
|
||||||
|
activeThreads++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runWork()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
UpdateItem toProcess;
|
||||||
|
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
toProcess = queue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
toProcess.PerformUpdate(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (queue)
|
||||||
|
activeThreads--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateItem
|
||||||
|
{
|
||||||
|
public Task Task => tcs.Task;
|
||||||
|
|
||||||
|
private readonly BeatmapInfo beatmap;
|
||||||
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
public UpdateItem(BeatmapInfo beatmap, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
this.cancellationToken = cancellationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PerformUpdate(IAPIProvider api)
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
tcs.SetCanceled();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api?.State != APIState.Online)
|
||||||
|
{
|
||||||
|
tcs.SetResult(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log("Attempting online lookup for the missing values...", LoggingTarget.Database);
|
||||||
|
|
||||||
|
var req = new GetBeatmapRequest(beatmap);
|
||||||
|
|
||||||
|
req.Success += res =>
|
||||||
|
{
|
||||||
|
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
|
||||||
|
|
||||||
|
beatmap.Status = res.Status;
|
||||||
|
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
||||||
|
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||||
|
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||||
|
|
||||||
|
tcs.SetResult(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
req.Failure += e =>
|
||||||
|
{
|
||||||
|
Logger.Log($"Failed ({e})", LoggingTarget.Database);
|
||||||
|
|
||||||
|
tcs.SetResult(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
req.Perform(api);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,17 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.IO.File;
|
using osu.Framework.IO.File;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
@ -109,8 +112,11 @@ namespace osu.Game.Database
|
|||||||
a.Invoke();
|
a.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly ThreadedTaskScheduler importScheduler;
|
||||||
|
|
||||||
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
|
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
|
||||||
{
|
{
|
||||||
|
importScheduler = new ThreadedTaskScheduler(16, $"{GetType().ReadableName()}.Import");
|
||||||
ContextFactory = contextFactory;
|
ContextFactory = contextFactory;
|
||||||
|
|
||||||
ModelStore = modelStore;
|
ModelStore = modelStore;
|
||||||
@ -130,92 +136,84 @@ namespace osu.Game.Database
|
|||||||
/// This will post notifications tracking progress.
|
/// This will post notifications tracking progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="paths">One or more archive locations on disk.</param>
|
/// <param name="paths">One or more archive locations on disk.</param>
|
||||||
public void Import(params string[] paths)
|
public async Task Import(params string[] paths)
|
||||||
{
|
{
|
||||||
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
|
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
|
||||||
|
|
||||||
PostNotification?.Invoke(notification);
|
PostNotification?.Invoke(notification);
|
||||||
Import(notification, paths);
|
|
||||||
|
await Import(notification, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Import(ProgressNotification notification, params string[] paths)
|
protected async Task Import(ProgressNotification notification, params string[] paths)
|
||||||
{
|
{
|
||||||
notification.Progress = 0;
|
notification.Progress = 0;
|
||||||
notification.Text = "Import is initialising...";
|
notification.Text = "Import is initialising...";
|
||||||
|
|
||||||
var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
|
var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
|
||||||
|
|
||||||
List<TModel> imported = new List<TModel>();
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
int current = 0;
|
int current = 0;
|
||||||
|
|
||||||
foreach (string path in paths)
|
foreach (string path in paths)
|
||||||
{
|
{
|
||||||
if (notification.State == ProgressNotificationState.Cancelled)
|
tasks.Add(Import(path, notification.CancellationToken).ContinueWith(t =>
|
||||||
// user requested abort
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var text = "Importing ";
|
lock (notification)
|
||||||
|
|
||||||
if (path.Length > 1)
|
|
||||||
text += $"{++current} of {paths.Length} {term}s..";
|
|
||||||
else
|
|
||||||
text += $"{term}..";
|
|
||||||
|
|
||||||
// only show the filename if it isn't a temporary one (as those look ugly).
|
|
||||||
if (!path.Contains(Path.GetTempPath()))
|
|
||||||
text += $"\n{Path.GetFileName(path)}";
|
|
||||||
|
|
||||||
notification.Text = text;
|
|
||||||
|
|
||||||
imported.Add(Import(path));
|
|
||||||
|
|
||||||
notification.Progress = (float)current / paths.Length;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
e = e.InnerException ?? e;
|
|
||||||
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imported.Count == 0)
|
|
||||||
{
|
|
||||||
notification.Text = "Import failed!";
|
|
||||||
notification.State = ProgressNotificationState.Cancelled;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
notification.CompletionText = imported.Count == 1
|
|
||||||
? $"Imported {imported.First()}!"
|
|
||||||
: $"Imported {current} {term}s!";
|
|
||||||
|
|
||||||
if (imported.Count > 0 && PresentImport != null)
|
|
||||||
{
|
|
||||||
notification.CompletionText += " Click to view.";
|
|
||||||
notification.CompletionClickAction = () =>
|
|
||||||
{
|
{
|
||||||
PresentImport?.Invoke(imported);
|
current++;
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.State = ProgressNotificationState.Completed;
|
notification.Text = $"Imported {current} of {paths.Length} {term}s";
|
||||||
|
notification.Progress = (float)current / paths.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.Exception != null)
|
||||||
|
{
|
||||||
|
var e = t.Exception.InnerException ?? t.Exception;
|
||||||
|
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
|
// if (imported.Count == 0)
|
||||||
|
// {
|
||||||
|
// notification.Text = "Import failed!";
|
||||||
|
// notification.State = ProgressNotificationState.Cancelled;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// notification.CompletionText = imported.Count == 1
|
||||||
|
// ? $"Imported {imported.First()}!"
|
||||||
|
// : $"Imported {current} {term}s!";
|
||||||
|
//
|
||||||
|
// if (imported.Count > 0 && PresentImport != null)
|
||||||
|
// {
|
||||||
|
// notification.CompletionText += " Click to view.";
|
||||||
|
// notification.CompletionClickAction = () =>
|
||||||
|
// {
|
||||||
|
// PresentImport?.Invoke(imported);
|
||||||
|
// return true;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// notification.State = ProgressNotificationState.Completed;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import one <see cref="TModel"/> from the filesystem and delete the file on success.
|
/// Import one <see cref="TModel"/> from the filesystem and delete the file on success.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The archive location on disk.</param>
|
/// <param name="path">The archive location on disk.</param>
|
||||||
|
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||||
/// <returns>The imported model, if successful.</returns>
|
/// <returns>The imported model, if successful.</returns>
|
||||||
public TModel Import(string path)
|
public async Task<TModel> Import(string path, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
TModel import;
|
TModel import;
|
||||||
using (ArchiveReader reader = getReaderFrom(path))
|
using (ArchiveReader reader = getReaderFrom(path))
|
||||||
import = Import(reader);
|
import = await Import(reader, cancellationToken);
|
||||||
|
|
||||||
// We may or may not want to delete the file depending on where it is stored.
|
// We may or may not want to delete the file depending on where it is stored.
|
||||||
// e.g. reconstructing/repairing database with items from default storage.
|
// e.g. reconstructing/repairing database with items from default storage.
|
||||||
@ -243,7 +241,8 @@ namespace osu.Game.Database
|
|||||||
/// Import an item from an <see cref="ArchiveReader"/>.
|
/// Import an item from an <see cref="ArchiveReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">The archive to be imported.</param>
|
/// <param name="archive">The archive to be imported.</param>
|
||||||
public TModel Import(ArchiveReader archive)
|
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||||
|
public async Task<TModel> Import(ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -253,7 +252,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
model.Hash = computeHash(archive);
|
model.Hash = computeHash(archive);
|
||||||
|
|
||||||
return Import(model, archive);
|
return await Import(model, archive, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -288,7 +287,8 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The model to be imported.</param>
|
/// <param name="item">The model to be imported.</param>
|
||||||
/// <param name="archive">An optional archive to use for model population.</param>
|
/// <param name="archive">An optional archive to use for model population.</param>
|
||||||
public TModel Import(TModel item, ArchiveReader archive = null)
|
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||||
|
public async Task<TModel> Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
|
||||||
{
|
{
|
||||||
delayEvents();
|
delayEvents();
|
||||||
|
|
||||||
@ -296,17 +296,31 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
Logger.Log($"Importing {item}...", LoggingTarget.Database);
|
Logger.Log($"Importing {item}...", LoggingTarget.Database);
|
||||||
|
|
||||||
|
if (archive != null)
|
||||||
|
item.Files = createFileInfos(archive, Files);
|
||||||
|
|
||||||
|
var localItem = item;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Populate(item, archive, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return item = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!Delete(localItem))
|
||||||
|
Files.Dereference(localItem.Files.Select(f => f.FileInfo).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}");
|
if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}");
|
||||||
|
|
||||||
if (archive != null)
|
|
||||||
item.Files = createFileInfos(archive, Files);
|
|
||||||
|
|
||||||
Populate(item, archive);
|
|
||||||
|
|
||||||
var existing = CheckForExisting(item);
|
var existing = CheckForExisting(item);
|
||||||
|
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
@ -332,6 +346,9 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
if (!Delete(item))
|
||||||
|
Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray());
|
||||||
|
|
||||||
write.Errors.Add(e);
|
write.Errors.Add(e);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@ -351,7 +368,7 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}, CancellationToken.None, TaskCreationOptions.None, importScheduler).Unwrap();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an update of the specified item.
|
/// Perform an update of the specified item.
|
||||||
@ -516,24 +533,24 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task ImportFromStableAsync()
|
public async Task ImportFromStableAsync()
|
||||||
{
|
{
|
||||||
var stable = GetStableStorage?.Invoke();
|
var stable = GetStableStorage?.Invoke();
|
||||||
|
|
||||||
if (stable == null)
|
if (stable == null)
|
||||||
{
|
{
|
||||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stable.ExistsDirectory(ImportFromStablePath))
|
if (!stable.ExistsDirectory(ImportFromStablePath))
|
||||||
{
|
{
|
||||||
// This handles situations like when the user does not have a Skins folder
|
// This handles situations like when the user does not have a Skins folder
|
||||||
Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
|
await Task.Run(async () => await Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -552,9 +569,8 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model to populate.</param>
|
/// <param name="model">The model to populate.</param>
|
||||||
/// <param name="archive">The archive to use as a reference for population. May be null.</param>
|
/// <param name="archive">The archive to use as a reference for population. May be null.</param>
|
||||||
protected virtual void Populate(TModel model, [CanBeNull] ArchiveReader archive)
|
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||||
{
|
protected virtual async Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => await Task.CompletedTask;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform any final actions before the import to database executes.
|
/// Perform any final actions before the import to database executes.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -12,7 +14,7 @@ namespace osu.Game.Database
|
|||||||
/// Import the specified paths.
|
/// Import the specified paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="paths">The files which should be imported.</param>
|
/// <param name="paths">The files which should be imported.</param>
|
||||||
void Import(params string[] paths);
|
Task Import(params string[] paths);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An array of accepted file extensions (in the standard format of ".abc").
|
/// An array of accepted file extensions (in the standard format of ".abc").
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.IPC
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (importer.HandledExtensions.Contains(Path.GetExtension(path)?.ToLowerInvariant()))
|
if (importer.HandledExtensions.Contains(Path.GetExtension(path)?.ToLowerInvariant()))
|
||||||
importer.Import(path);
|
await importer.Import(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -268,13 +269,13 @@ namespace osu.Game
|
|||||||
|
|
||||||
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
||||||
|
|
||||||
public void Import(params string[] paths)
|
public async Task Import(params string[] paths)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
|
var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
|
||||||
|
|
||||||
foreach (var importer in fileImporters)
|
foreach (var importer in fileImporters)
|
||||||
if (importer.HandledExtensions.Contains(extension))
|
if (importer.HandledExtensions.Contains(extension))
|
||||||
importer.Import(paths);
|
await importer.Import(paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
|
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -36,6 +37,10 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
State = state;
|
State = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
public CancellationToken CancellationToken => cancellationTokenSource.Token;
|
||||||
|
|
||||||
public virtual ProgressNotificationState State
|
public virtual ProgressNotificationState State
|
||||||
{
|
{
|
||||||
get => state;
|
get => state;
|
||||||
@ -62,6 +67,8 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ProgressNotificationState.Cancelled:
|
case ProgressNotificationState.Cancelled:
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
|
||||||
Light.Colour = colourCancelled;
|
Light.Colour = colourCancelled;
|
||||||
Light.Pulsate = false;
|
Light.Pulsate = false;
|
||||||
progressBar.Active = false;
|
progressBar.Active = false;
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
if (setInfo == null)
|
if (setInfo == null)
|
||||||
{
|
{
|
||||||
// we need to import the default menu background beatmap
|
// we need to import the default menu background beatmap
|
||||||
setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz"));
|
setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")).Result;
|
||||||
|
|
||||||
setInfo.Protected = true;
|
setInfo.Protected = true;
|
||||||
beatmaps.Update(setInfo);
|
beatmaps.Update(setInfo);
|
||||||
|
@ -279,7 +279,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
var score = CreateScore();
|
var score = CreateScore();
|
||||||
if (DrawableRuleset.ReplayScore == null)
|
if (DrawableRuleset.ReplayScore == null)
|
||||||
scoreManager.Import(score);
|
scoreManager.Import(score).Wait();
|
||||||
|
|
||||||
this.Push(CreateResults(score));
|
this.Push(CreateResults(score));
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
@ -71,9 +73,9 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
|
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
|
||||||
|
|
||||||
protected override void Populate(SkinInfo model, ArchiveReader archive)
|
protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
base.Populate(model, archive);
|
await base.Populate(model, archive, cancellationToken);
|
||||||
|
|
||||||
Skin reference = getSkin(model);
|
Skin reference = getSkin(model);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user