mirror of
https://github.com/ppy/osu.git
synced 2025-02-16 18:32:56 +08:00
Merge remote-tracking branch 'upstream/master' into generic-download-model-manager
This commit is contained in:
commit
951a5abccc
@ -120,7 +120,7 @@ namespace osu.Desktop.Overlays
|
|||||||
|
|
||||||
Activated = delegate
|
Activated = delegate
|
||||||
{
|
{
|
||||||
changelog.ShowBuild("lazer", version);
|
changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -11,7 +14,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
|
internal class OsuModGrow : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
|
||||||
{
|
{
|
||||||
public override string Name => "Grow";
|
public override string Name => "Grow";
|
||||||
|
|
||||||
@ -25,9 +28,16 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
|
||||||
|
|
||||||
|
public void ReadFromConfig(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
|
||||||
|
}
|
||||||
|
|
||||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
{
|
{
|
||||||
foreach (var drawable in drawables)
|
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
|
||||||
{
|
{
|
||||||
switch (drawable)
|
switch (drawable)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,9 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.IO;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
|
|
||||||
@ -21,14 +23,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 +40,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 +49,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 +61,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,17 +70,15 @@ 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);
|
||||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
|
||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
checkBeatmapSetCount(osu, 1);
|
||||||
|
checkSingleReferencedFileCount(osu, 18);
|
||||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
|
||||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -88,30 +88,41 @@ 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"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
int itemAddRemoveFireCount = 0;
|
||||||
|
int loggedExceptionCount = 0;
|
||||||
|
|
||||||
|
Logger.NewEntry += l =>
|
||||||
|
{
|
||||||
|
if (l.Target == LoggingTarget.Database && l.Exception != null)
|
||||||
|
Interlocked.Increment(ref loggedExceptionCount);
|
||||||
|
};
|
||||||
|
|
||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
int fireCount = 0;
|
|
||||||
|
|
||||||
// ReSharper disable once AccessToModifiedClosure
|
// ReSharper disable once AccessToModifiedClosure
|
||||||
manager.ItemAdded += (_, __) => fireCount++;
|
manager.ItemAdded += (_, __) => Interlocked.Increment(ref itemAddRemoveFireCount);
|
||||||
manager.ItemRemoved += _ => fireCount++;
|
manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
|
||||||
|
|
||||||
var imported = LoadOszIntoOsu(osu);
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
Assert.AreEqual(0, fireCount -= 1);
|
Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
|
||||||
|
|
||||||
imported.Hash += "-changed";
|
imported.Hash += "-changed";
|
||||||
manager.Update(imported);
|
manager.Update(imported);
|
||||||
|
|
||||||
Assert.AreEqual(0, fireCount -= 2);
|
Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
|
||||||
|
|
||||||
|
checkBeatmapSetCount(osu, 1);
|
||||||
|
checkBeatmapCount(osu, 12);
|
||||||
|
checkSingleReferencedFileCount(osu, 18);
|
||||||
|
|
||||||
var breakTemp = TestResources.GetTestBeatmapForImport();
|
var breakTemp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -127,19 +138,24 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
|
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
|
||||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => 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);
|
try
|
||||||
|
{
|
||||||
|
await manager.Import(breakTemp);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// 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, itemAddRemoveFireCount);
|
||||||
|
|
||||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
checkBeatmapSetCount(osu, 1);
|
||||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
checkBeatmapCount(osu, 12);
|
||||||
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
|
||||||
|
checkSingleReferencedFileCount(osu, 18);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, loggedExceptionCount);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -149,7 +165,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,19 +175,18 @@ 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);
|
||||||
|
|
||||||
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
|
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
|
||||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
checkBeatmapSetCount(osu, 1);
|
||||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -181,7 +196,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 +205,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 +224,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 +233,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 +244,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 +258,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 +299,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 +345,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 +354,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 +366,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();
|
||||||
|
|
||||||
@ -373,11 +388,32 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
manager.Delete(imported);
|
manager.Delete(imported);
|
||||||
|
|
||||||
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
|
checkBeatmapSetCount(osu, 0);
|
||||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
checkBeatmapSetCount(osu, 1, true);
|
||||||
|
checkSingleReferencedFileCount(osu, 0);
|
||||||
|
|
||||||
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
|
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
|
||||||
|
{
|
||||||
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
|
Assert.AreEqual(expected, includeDeletePending
|
||||||
|
? manager.QueryBeatmapSets(_ => true).ToList().Count
|
||||||
|
: manager.GetAllUsableBeatmapSets().Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBeatmapCount(OsuGameBase osu, int expected)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expected, osu.Dependencies.Get<BeatmapManager>().QueryBeatmaps(_ => true).ToList().Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expected, osu.Dependencies.Get<FileStore>().QueryFiles(f => f.ReferenceCount == 1).Count());
|
||||||
|
}
|
||||||
|
|
||||||
private OsuGameBase loadOsu(GameHost host)
|
private OsuGameBase loadOsu(GameHost host)
|
||||||
{
|
{
|
||||||
var osu = new OsuGameBase();
|
var osu = new OsuGameBase();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
Version = "2018.712.0",
|
Version = "2018.712.0",
|
||||||
DisplayVersion = "2018.712.0",
|
DisplayVersion = "2018.712.0",
|
||||||
UpdateStream = new APIUpdateStream { Name = "lazer" },
|
UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME },
|
||||||
ChangelogEntries = new List<APIChangelogEntry>
|
ChangelogEntries = new List<APIChangelogEntry>
|
||||||
{
|
{
|
||||||
new APIChangelogEntry
|
new APIChangelogEntry
|
||||||
|
@ -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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@ -12,10 +13,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneUserPanel : OsuTestScene
|
public class TestSceneUserPanel : OsuTestScene
|
||||||
{
|
{
|
||||||
|
private readonly UserPanel peppy;
|
||||||
|
|
||||||
public TestSceneUserPanel()
|
public TestSceneUserPanel()
|
||||||
{
|
{
|
||||||
UserPanel flyte;
|
UserPanel flyte;
|
||||||
UserPanel peppy;
|
|
||||||
Add(new FillFlowContainer
|
Add(new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -44,13 +47,31 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
|
|
||||||
flyte.Status.Value = new UserStatusOnline();
|
flyte.Status.Value = new UserStatusOnline();
|
||||||
peppy.Status.Value = new UserStatusSoloGame();
|
peppy.Status.Value = null;
|
||||||
|
}
|
||||||
|
|
||||||
AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
|
[Test]
|
||||||
AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
|
public void UserStatusesTests()
|
||||||
AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
|
{
|
||||||
AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
|
AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); });
|
||||||
AddStep(@"null status", () => { flyte.Status.Value = null; });
|
AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); });
|
||||||
|
AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); });
|
||||||
|
AddStep(@"null status", () => { peppy.Status.Value = null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void UserActivitiesTests()
|
||||||
|
{
|
||||||
|
Bindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||||
|
|
||||||
|
peppy.Activity.BindTo(activity);
|
||||||
|
|
||||||
|
AddStep("idle", () => { activity.Value = null; });
|
||||||
|
AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); });
|
||||||
|
AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); });
|
||||||
|
AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); });
|
||||||
|
AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); });
|
||||||
|
AddStep("modding", () => { activity.Value = new UserActivity.Modding(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneSettings : OsuTestScene
|
public class TestSceneSettingsPanel : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly SettingsPanel settings;
|
private readonly SettingsPanel settings;
|
||||||
private readonly DialogOverlay dialogOverlay;
|
private readonly DialogOverlay dialogOverlay;
|
||||||
|
|
||||||
public TestSceneSettings()
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(SettingsFooter),
|
||||||
|
typeof(SettingsOverlay),
|
||||||
|
};
|
||||||
|
|
||||||
|
public TestSceneSettingsPanel()
|
||||||
{
|
{
|
||||||
settings = new SettingsOverlay
|
settings = new SettingsOverlay
|
||||||
{
|
{
|
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
changeRuleset(2);
|
changeRuleset(2);
|
||||||
importForRuleset(0);
|
addRulesetImportStep(0);
|
||||||
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
|
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +147,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
changeRuleset(2);
|
changeRuleset(2);
|
||||||
importForRuleset(2);
|
addRulesetImportStep(2);
|
||||||
importForRuleset(1);
|
addRulesetImportStep(1);
|
||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
|
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
|
||||||
|
|
||||||
changeRuleset(1);
|
changeRuleset(1);
|
||||||
@ -210,7 +210,52 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("start not requested", () => !startRequested);
|
AddAssert("start not requested", () => !startRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())));
|
[Test]
|
||||||
|
public void TestAddNewBeatmapWhileSelectingRandom()
|
||||||
|
{
|
||||||
|
const int test_count = 10;
|
||||||
|
int beatmapChangedCount = 0;
|
||||||
|
int debounceCount = 0;
|
||||||
|
createSongSelect();
|
||||||
|
AddStep("Setup counters", () =>
|
||||||
|
{
|
||||||
|
beatmapChangedCount = 0;
|
||||||
|
debounceCount = 0;
|
||||||
|
songSelect.Carousel.SelectionChanged += _ => beatmapChangedCount++;
|
||||||
|
});
|
||||||
|
AddRepeatStep($"Create beatmaps {test_count} times", () =>
|
||||||
|
{
|
||||||
|
importForRuleset(0);
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
// Wait for debounce
|
||||||
|
songSelect.Carousel.SelectNextRandom();
|
||||||
|
++debounceCount;
|
||||||
|
}, 400);
|
||||||
|
}, test_count);
|
||||||
|
|
||||||
|
AddUntilStep("Debounce limit reached", () => debounceCount == test_count);
|
||||||
|
|
||||||
|
// The selected beatmap should have changed an additional 2 times since both initially loading songselect and the first import also triggers selectionChanged
|
||||||
|
AddAssert($"Beatmap changed {test_count + 2} times", () => beatmapChangedCount == test_count + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHideSetSelectsCorrectBeatmap()
|
||||||
|
{
|
||||||
|
int? previousID = null;
|
||||||
|
createSongSelect();
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last()));
|
||||||
|
AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID);
|
||||||
|
AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First()));
|
||||||
|
AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
|
||||||
|
|
||||||
|
private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
|
||||||
|
|
||||||
private static int importId;
|
private static int importId;
|
||||||
private int getImportId() => ++importId;
|
private int getImportId() => ++importId;
|
||||||
@ -232,7 +277,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
|
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
|
||||||
|
|
||||||
for (int i = 0; i < 100; i += 10)
|
for (int i = 0; i < 100; i += 10)
|
||||||
manager.Import(createTestBeatmapSet(i, usableRulesets));
|
manager.Import(createTestBeatmapSet(i, usableRulesets)).Wait();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ScoreInfo> Scores { get; set; }
|
public List<ScoreInfo> Scores { get; set; }
|
||||||
|
|
||||||
public override string ToString() => $"{Metadata} [{Version}]";
|
public override string ToString() => $"{Metadata} [{Version}]".Trim();
|
||||||
|
|
||||||
public bool Equals(BeatmapInfo other)
|
public bool Equals(BeatmapInfo other)
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,8 @@ 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 Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
@ -13,6 +15,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;
|
||||||
@ -58,6 +61,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
private readonly GameHost host;
|
private readonly GameHost host;
|
||||||
|
|
||||||
|
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, api, new BeatmapStore(contextFactory), host)
|
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
||||||
@ -72,11 +77,13 @@ 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 ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, object[] options) => new DownloadBeatmapSetRequest(set, (options?.FirstOrDefault() as bool?) ?? false);
|
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, object[] options) => new DownloadBeatmapSetRequest(set, (options?.FirstOrDefault() as bool?) ?? false);
|
||||||
|
|
||||||
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive)
|
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
||||||
@ -92,8 +99,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
validateOnlineIds(beatmapSet);
|
validateOnlineIds(beatmapSet);
|
||||||
|
|
||||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
return updateQueue.UpdateAsync(beatmapSet, cancellationToken);
|
||||||
fetchAndPopulateOnlineValues(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PreImport(BeatmapSetInfo beatmapSet)
|
protected override void PreImport(BeatmapSetInfo beatmapSet)
|
||||||
@ -110,7 +116,7 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
Delete(existingOnlineId);
|
Delete(existingOnlineId);
|
||||||
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
||||||
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,47 +296,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>
|
||||||
@ -348,5 +313,55 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override Texture GetBackground() => null;
|
protected override Texture GetBackground() => null;
|
||||||
protected override Track GetTrack() => null;
|
protected override Track GetTrack() => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BeatmapUpdateQueue
|
||||||
|
{
|
||||||
|
private readonly IAPIProvider api;
|
||||||
|
|
||||||
|
private const int update_queue_request_concurrency = 4;
|
||||||
|
|
||||||
|
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue));
|
||||||
|
|
||||||
|
public BeatmapUpdateQueue(IAPIProvider api)
|
||||||
|
{
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (api?.State != APIState.Online)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
LogForModel(beatmapSet, "Performing online lookups...");
|
||||||
|
return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: expose this when we need to do individual difficulty lookups.
|
||||||
|
protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken)
|
||||||
|
=> Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler);
|
||||||
|
|
||||||
|
private void update(BeatmapSetInfo set, BeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
if (api?.State != APIState.Online)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var req = new GetBeatmapRequest(beatmap);
|
||||||
|
|
||||||
|
req.Success += res =>
|
||||||
|
{
|
||||||
|
LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
|
||||||
|
|
||||||
|
beatmap.Status = res.Status;
|
||||||
|
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
||||||
|
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||||
|
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||||
|
};
|
||||||
|
|
||||||
|
req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap}", e); };
|
||||||
|
|
||||||
|
// intentionally blocking to limit web request concurrency
|
||||||
|
req.Perform(api);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ 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;
|
||||||
@ -13,6 +14,7 @@ using osu.Framework.Extensions;
|
|||||||
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;
|
||||||
@ -29,7 +31,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>
|
public abstract class ArchiveModelManager<TModel, TFileModel> : ArchiveModelManager, ICanAcceptFiles, IModelManager<TModel>
|
||||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||||
where TFileModel : INamedFileInfo, new()
|
where TFileModel : INamedFileInfo, new()
|
||||||
{
|
{
|
||||||
@ -130,56 +132,50 @@ 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 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);
|
|
||||||
|
return 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()}";
|
|
||||||
|
|
||||||
List<TModel> imported = new List<TModel>();
|
|
||||||
|
|
||||||
int current = 0;
|
int current = 0;
|
||||||
|
|
||||||
foreach (string path in paths)
|
var imported = new List<TModel>();
|
||||||
|
|
||||||
|
await Task.WhenAll(paths.Select(async path =>
|
||||||
{
|
{
|
||||||
if (notification.State == ProgressNotificationState.Cancelled)
|
notification.CancellationToken.ThrowIfCancellationRequested();
|
||||||
// user requested abort
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var text = "Importing ";
|
var model = await Import(path, notification.CancellationToken);
|
||||||
|
|
||||||
if (path.Length > 1)
|
lock (imported)
|
||||||
text += $"{++current} of {paths.Length} {term}s..";
|
{
|
||||||
else
|
imported.Add(model);
|
||||||
text += $"{term}..";
|
current++;
|
||||||
|
|
||||||
// only show the filename if it isn't a temporary one (as those look ugly).
|
notification.Text = $"Imported {current} of {paths.Length} {humanisedModelName}s";
|
||||||
if (!path.Contains(Path.GetTempPath()))
|
notification.Progress = (float)current / paths.Length;
|
||||||
text += $"\n{Path.GetFileName(path)}";
|
}
|
||||||
|
}
|
||||||
notification.Text = text;
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
imported.Add(Import(path));
|
throw;
|
||||||
|
|
||||||
notification.Progress = (float)current / paths.Length;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
e = e.InnerException ?? e;
|
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})", LoggingTarget.Database);
|
||||||
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
|
|
||||||
if (imported.Count == 0)
|
if (imported.Count == 0)
|
||||||
{
|
{
|
||||||
@ -190,7 +186,7 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
notification.CompletionText = imported.Count == 1
|
notification.CompletionText = imported.Count == 1
|
||||||
? $"Imported {imported.First()}!"
|
? $"Imported {imported.First()}!"
|
||||||
: $"Imported {current} {term}s!";
|
: $"Imported {current} {humanisedModelName}s!";
|
||||||
|
|
||||||
if (imported.Count > 0 && PresentImport != null)
|
if (imported.Count > 0 && PresentImport != null)
|
||||||
{
|
{
|
||||||
@ -210,12 +206,15 @@ namespace osu.Game.Database
|
|||||||
/// 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)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
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.
|
||||||
@ -228,7 +227,7 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})");
|
LogForModel(import, $@"Could not delete original file after import ({Path.GetFileName(path)})", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return import;
|
return import;
|
||||||
@ -243,23 +242,32 @@ 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 Task<TModel> Import(ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
TModel model = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var model = CreateModel(archive);
|
model = CreateModel(archive);
|
||||||
|
|
||||||
if (model == null) return null;
|
if (model == null) return null;
|
||||||
|
|
||||||
model.Hash = computeHash(archive);
|
model.Hash = computeHash(archive);
|
||||||
|
}
|
||||||
return Import(model, archive);
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, $"Model creation of {archive.Name} failed.", LoggingTarget.Database);
|
LogForModel(model, $"Model creation of {archive.Name} failed.", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Import(model, archive, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -269,6 +277,16 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract string[] HashableFileTypes { get; }
|
protected abstract string[] HashableFileTypes { get; }
|
||||||
|
|
||||||
|
protected static void LogForModel(TModel model, string message, Exception e = null)
|
||||||
|
{
|
||||||
|
string prefix = $"[{(model?.Hash ?? "?????").Substring(0, 5)}]";
|
||||||
|
|
||||||
|
if (e != null)
|
||||||
|
Logger.Error(e, $"{prefix} {message}", LoggingTarget.Database);
|
||||||
|
else
|
||||||
|
Logger.Log($"{prefix} {message}", LoggingTarget.Database);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a SHA-2 hash from the provided archive based on file content of all files matching <see cref="HashableFileTypes"/>.
|
/// Create a SHA-2 hash from the provided archive based on file content of all files matching <see cref="HashableFileTypes"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -288,13 +306,30 @@ 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 () =>
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
delayEvents();
|
delayEvents();
|
||||||
|
|
||||||
|
void rollback()
|
||||||
|
{
|
||||||
|
if (!Delete(item))
|
||||||
|
{
|
||||||
|
// We may have not yet added the model to the underlying table, but should still clean up files.
|
||||||
|
LogForModel(item, "Dereferencing files for incomplete import.");
|
||||||
|
Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.Log($"Importing {item}...", LoggingTarget.Database);
|
LogForModel(item, "Beginning import...");
|
||||||
|
|
||||||
|
item.Files = archive != null ? createFileInfos(archive, Files) : new List<TFileModel>();
|
||||||
|
|
||||||
|
await Populate(item, archive, cancellationToken);
|
||||||
|
|
||||||
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.
|
||||||
{
|
{
|
||||||
@ -302,11 +337,6 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
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)
|
||||||
@ -314,15 +344,17 @@ namespace osu.Game.Database
|
|||||||
if (CanUndelete(existing, item))
|
if (CanUndelete(existing, item))
|
||||||
{
|
{
|
||||||
Undelete(existing);
|
Undelete(existing);
|
||||||
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
|
LogForModel(item, $"Found existing {humanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
||||||
handleEvent(() => ItemAdded?.Invoke(existing, true));
|
handleEvent(() => ItemAdded?.Invoke(existing, true));
|
||||||
|
|
||||||
|
// existing item will be used; rollback new import and exit early.
|
||||||
|
rollback();
|
||||||
|
flushEvents(true);
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
Delete(existing);
|
||||||
Delete(existing);
|
ModelStore.PurgeDeletable(s => s.ID == existing.ID);
|
||||||
ModelStore.PurgeDeletable(s => s.ID == existing.ID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PreImport(item);
|
PreImport(item);
|
||||||
@ -337,21 +369,21 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log($"Import of {item} successfully completed!", LoggingTarget.Database);
|
LogForModel(item, "Import successfully completed!");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, $"Import of {item} failed and has been rolled back.", LoggingTarget.Database);
|
if (!(e is TaskCanceledException))
|
||||||
item = null;
|
LogForModel(item, "Database import or population failed and has been rolled back.", e);
|
||||||
}
|
|
||||||
finally
|
rollback();
|
||||||
{
|
flushEvents(false);
|
||||||
// we only want to flush events after we've confirmed the write context didn't have any errors.
|
throw;
|
||||||
flushEvents(item != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flushEvents(true);
|
||||||
return item;
|
return item;
|
||||||
}
|
}, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an update of the specified item.
|
/// Perform an update of the specified item.
|
||||||
@ -533,7 +565,7 @@ namespace osu.Game.Database
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
|
return Task.Run(async () => await Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -552,9 +584,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 Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform any final actions before the import to database executes.
|
/// Perform any final actions before the import to database executes.
|
||||||
@ -582,6 +613,8 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
||||||
|
|
||||||
|
private string humanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -599,4 +632,18 @@ namespace osu.Game.Database
|
|||||||
throw new InvalidFormatException($"{path} is not a valid archive");
|
throw new InvalidFormatException($"{path} is not a valid archive");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class ArchiveModelManager
|
||||||
|
{
|
||||||
|
private const int import_queue_request_concurrency = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly.
|
||||||
|
/// It is mainly being used as a queue mechanism for large imports.
|
||||||
|
/// </remarks>
|
||||||
|
protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// 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 osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -97,9 +97,10 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
request.Success += filename =>
|
request.Success += filename =>
|
||||||
{
|
{
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(async () =>
|
||||||
{
|
{
|
||||||
Import(notification, filename);
|
// This gets scheduled back to the update thread, but we want the import to run in the background.
|
||||||
|
await Import(notification, filename);
|
||||||
currentDownloads.Remove(request);
|
currentDownloads.Remove(request);
|
||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
};
|
};
|
||||||
|
@ -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").
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
// 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.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -27,6 +30,13 @@ namespace osu.Game.IO
|
|||||||
Store = new StorageBackedResourceStore(Storage);
|
Store = new StorageBackedResourceStore(Storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a lookup query on available <see cref="FileInfo"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The query.</param>
|
||||||
|
/// <returns>Results from the provided query.</returns>
|
||||||
|
public IEnumerable<FileInfo> QueryFiles(Expression<Func<FileInfo, bool>> query) => ContextFactory.Get().Set<FileInfo>().AsNoTracking().Where(f => f.ReferenceCount > 0).Where(query);
|
||||||
|
|
||||||
public FileInfo Add(Stream data, bool reference = true)
|
public FileInfo Add(Stream data, bool reference = true)
|
||||||
{
|
{
|
||||||
using (var usage = ContextFactory.GetForWrite())
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public Bindable<User> LocalUser { get; } = new Bindable<User>(createGuestUser());
|
public Bindable<User> LocalUser { get; } = new Bindable<User>(createGuestUser());
|
||||||
|
|
||||||
|
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
||||||
|
|
||||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||||
@ -55,6 +57,12 @@ namespace osu.Game.Online.API
|
|||||||
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
||||||
authentication.Token.ValueChanged += onTokenChanged;
|
authentication.Token.ValueChanged += onTokenChanged;
|
||||||
|
|
||||||
|
LocalUser.BindValueChanged(u =>
|
||||||
|
{
|
||||||
|
u.OldValue?.Activity.UnbindFrom(Activity);
|
||||||
|
u.NewValue.Activity.BindTo(Activity);
|
||||||
|
}, true);
|
||||||
|
|
||||||
var thread = new Thread(run)
|
var thread = new Thread(run)
|
||||||
{
|
{
|
||||||
Name = "APIAccess",
|
Name = "APIAccess",
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Online.API
|
|||||||
Id = 1001,
|
Id = 1001,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
public bool IsLoggedIn => true;
|
public bool IsLoggedIn => true;
|
||||||
|
|
||||||
public string ProvidedUsername => LocalUser.Value.Username;
|
public string ProvidedUsername => LocalUser.Value.Username;
|
||||||
@ -41,6 +43,15 @@ namespace osu.Game.Online.API
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DummyAPIAccess()
|
||||||
|
{
|
||||||
|
LocalUser.BindValueChanged(u =>
|
||||||
|
{
|
||||||
|
u.OldValue?.Activity.UnbindFrom(Activity);
|
||||||
|
u.NewValue.Activity.BindTo(Activity);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Queue(APIRequest request)
|
public virtual void Queue(APIRequest request)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,11 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Bindable<User> LocalUser { get; }
|
Bindable<User> LocalUser { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current user's activity.
|
||||||
|
/// </summary>
|
||||||
|
Bindable<UserActivity> Activity { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether the local user is logged in.
|
/// Returns whether the local user is logged in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
case "cuttingedge":
|
case "cuttingedge":
|
||||||
return new Color4(238, 170, 0, 255);
|
return new Color4(238, 170, 0, 255);
|
||||||
|
|
||||||
case "lazer":
|
case OsuGameBase.CLIENT_STREAM_NAME:
|
||||||
return new Color4(237, 18, 33, 255);
|
return new Color4(237, 18, 33, 255);
|
||||||
|
|
||||||
case "web":
|
case "web":
|
||||||
|
@ -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;
|
||||||
@ -44,6 +45,8 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class OsuGameBase : Framework.Game, ICanAcceptFiles
|
public class OsuGameBase : Framework.Game, ICanAcceptFiles
|
||||||
{
|
{
|
||||||
|
public const string CLIENT_STREAM_NAME = "lazer";
|
||||||
|
|
||||||
protected OsuConfigManager LocalConfig;
|
protected OsuConfigManager LocalConfig;
|
||||||
|
|
||||||
protected BeatmapManager BeatmapManager;
|
protected BeatmapManager BeatmapManager;
|
||||||
@ -268,13 +271,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;
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
{
|
{
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
LabelText = "Increase visibility of first object with \"Hidden\" mod",
|
LabelText = "Increase visibility of first object when visual impairment mods are enabled",
|
||||||
Bindable = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility)
|
Bindable = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -154,8 +154,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
};
|
};
|
||||||
|
|
||||||
panel.Status.BindTo(api.LocalUser.Value.Status);
|
panel.Status.BindTo(api.LocalUser.Value.Status);
|
||||||
|
panel.Activity.BindTo(api.LocalUser.Value.Activity);
|
||||||
|
|
||||||
dropdown.Current.ValueChanged += action =>
|
dropdown.Current.BindValueChanged(action =>
|
||||||
{
|
{
|
||||||
switch (action.NewValue)
|
switch (action.NewValue)
|
||||||
{
|
{
|
||||||
@ -178,9 +179,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
api.Logout();
|
api.Logout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}, true);
|
||||||
dropdown.Current.TriggerChange();
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,11 +10,14 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
private OsuCheckbox checkbox;
|
private OsuCheckbox checkbox;
|
||||||
|
|
||||||
|
private string labelText;
|
||||||
|
|
||||||
protected override Drawable CreateControl() => checkbox = new OsuCheckbox();
|
protected override Drawable CreateControl() => checkbox = new OsuCheckbox();
|
||||||
|
|
||||||
public override string LabelText
|
public override string LabelText
|
||||||
{
|
{
|
||||||
set => checkbox.LabelText = value;
|
get => labelText;
|
||||||
|
set => checkbox.LabelText = labelText = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -58,15 +59,49 @@ namespace osu.Game.Overlays.Settings
|
|||||||
Text = game.Name,
|
Text = game.Name,
|
||||||
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold),
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new BuildDisplay(game.Version, DebugUtils.IsDebug)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Font = OsuFont.GetFont(size: 14),
|
}
|
||||||
Text = game.Version,
|
|
||||||
Colour = DebugUtils.IsDebug ? colours.Red : Color4.White,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BuildDisplay : OsuAnimatedButton
|
||||||
|
{
|
||||||
|
private readonly string version;
|
||||||
|
private readonly bool isDebug;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
public BuildDisplay(string version, bool isDebug)
|
||||||
|
{
|
||||||
|
this.version = version;
|
||||||
|
this.isDebug = isDebug;
|
||||||
|
|
||||||
|
Content.RelativeSizeAxes = Axes.Y;
|
||||||
|
Content.AutoSizeAxes = AutoSizeAxes = Axes.X;
|
||||||
|
Height = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(ChangelogOverlay changelog)
|
||||||
|
{
|
||||||
|
if (!isDebug)
|
||||||
|
Action = () => changelog?.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
|
||||||
|
|
||||||
|
Add(new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.GetFont(size: 16),
|
||||||
|
|
||||||
|
Text = version,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding(5),
|
||||||
|
Colour = isDebug ? colours.Red : Color4.White,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,7 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
|
|
||||||
panel.Status.BindTo(u.Status);
|
panel.Status.BindTo(u.Status);
|
||||||
|
panel.Activity.BindTo(u.Activity);
|
||||||
return panel;
|
return panel;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,7 @@ using osu.Game.Screens.Edit.Design;
|
|||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
{
|
{
|
||||||
@ -47,6 +48,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
private GameHost host;
|
private GameHost host;
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo);
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -15,6 +15,8 @@ using osu.Game.Input.Bindings;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Screens
|
namespace osu.Game.Screens
|
||||||
@ -52,6 +54,30 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
protected new OsuGameBase Game => base.Game as OsuGameBase;
|
protected new OsuGameBase Game => base.Game as OsuGameBase;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="UserActivity"/> to set the user's activity automatically to when this screen is entered
|
||||||
|
/// <para>This <see cref="Activity"/> will be automatically set to <see cref="InitialActivity"/> for this screen on entering unless
|
||||||
|
/// <see cref="Activity"/> is manually set before.</para>
|
||||||
|
/// </summary>
|
||||||
|
protected virtual UserActivity InitialActivity => null;
|
||||||
|
|
||||||
|
private UserActivity activity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current <see cref="UserActivity"/> for this screen.
|
||||||
|
/// </summary>
|
||||||
|
protected UserActivity Activity
|
||||||
|
{
|
||||||
|
get => activity;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == activity) return;
|
||||||
|
|
||||||
|
activity = value;
|
||||||
|
updateActivity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children).
|
/// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -90,6 +116,9 @@ namespace osu.Game.Screens
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private OsuLogo logo { get; set; }
|
private OsuLogo logo { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
protected OsuScreen()
|
protected OsuScreen()
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
@ -123,12 +152,15 @@ namespace osu.Game.Screens
|
|||||||
sampleExit?.Play();
|
sampleExit?.Play();
|
||||||
applyArrivingDefaults(true);
|
applyArrivingDefaults(true);
|
||||||
|
|
||||||
|
updateActivity();
|
||||||
|
|
||||||
base.OnResuming(last);
|
base.OnResuming(last);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuspending(IScreen next)
|
public override void OnSuspending(IScreen next)
|
||||||
{
|
{
|
||||||
base.OnSuspending(next);
|
base.OnSuspending(next);
|
||||||
|
|
||||||
onSuspendingLogo();
|
onSuspendingLogo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +170,9 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
backgroundStack?.Push(localBackground = CreateBackground());
|
backgroundStack?.Push(localBackground = CreateBackground());
|
||||||
|
|
||||||
|
if (activity == null)
|
||||||
|
Activity = InitialActivity;
|
||||||
|
|
||||||
base.OnEntering(last);
|
base.OnEntering(last);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +190,12 @@ namespace osu.Game.Screens
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateActivity()
|
||||||
|
{
|
||||||
|
if (api != null)
|
||||||
|
api.Activity.Value = activity;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when this screen was entered or resumed and the logo state is required to be adjusted.
|
/// Fired when this screen was entered or resumed and the logo state is required to be adjusted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +27,7 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -34,6 +35,8 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
protected override bool AllowBackButton => false; // handled by HoldForMenuButton
|
protected override bool AllowBackButton => false; // handled by HoldForMenuButton
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||||
|
|
||||||
public override float BackgroundParallaxAmount => 0.1f;
|
public override float BackgroundParallaxAmount => 0.1f;
|
||||||
|
|
||||||
public override bool HideOverlaysOnEnter => true;
|
public override bool HideOverlaysOnEnter => true;
|
||||||
@ -279,7 +282,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));
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.PlayerSettings;
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.Play
|
|||||||
private bool hideOverlays;
|
private bool hideOverlays;
|
||||||
public override bool HideOverlaysOnEnter => hideOverlays;
|
public override bool HideOverlaysOnEnter => hideOverlays;
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => null; //shows the previous screen status
|
||||||
|
|
||||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
protected override bool PlayResumeSound => false;
|
protected override bool PlayResumeSound => false;
|
||||||
|
@ -152,9 +152,12 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
|
int? previouslySelectedID = null;
|
||||||
CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID);
|
CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID);
|
||||||
|
|
||||||
bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected;
|
// If the selected beatmap is about to be removed, store its ID so it can be re-selected if required
|
||||||
|
if (existingSet?.State?.Value == CarouselItemState.Selected)
|
||||||
|
previouslySelectedID = selectedBeatmap?.Beatmap.ID;
|
||||||
|
|
||||||
var newSet = createCarouselSet(beatmapSet);
|
var newSet = createCarouselSet(beatmapSet);
|
||||||
|
|
||||||
@ -172,8 +175,8 @@ namespace osu.Game.Screens.Select
|
|||||||
applyActiveCriteria(false, false);
|
applyActiveCriteria(false, false);
|
||||||
|
|
||||||
//check if we can/need to maintain our current selection.
|
//check if we can/need to maintain our current selection.
|
||||||
if (hadSelection)
|
if (previouslySelectedID != null)
|
||||||
select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet);
|
select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == previouslySelectedID) ?? newSet);
|
||||||
|
|
||||||
itemsCache.Invalidate();
|
itemsCache.Invalidate();
|
||||||
Schedule(() => BeatmapSetsChanged?.Invoke());
|
Schedule(() => BeatmapSetsChanged?.Invoke());
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Users;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
@ -18,6 +19,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
public override bool AllowExternalScreenChange => true;
|
public override bool AllowExternalScreenChange => true;
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,7 @@ using osuTK.Input;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
@ -256,8 +257,8 @@ namespace osu.Game.Screens.Select
|
|||||||
if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
|
if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
|
||||||
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
||||||
{
|
{
|
||||||
beatmaps.ImportFromStableAsync();
|
Task.Run(beatmaps.ImportFromStableAsync);
|
||||||
skins.ImportFromStableAsync();
|
Task.Run(skins.ImportFromStableAsync);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -594,11 +595,17 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
bindBindables();
|
bindBindables();
|
||||||
|
|
||||||
|
// If a selection was already obtained, do not attempt to update the selected beatmap.
|
||||||
|
if (Carousel.SelectedBeatmapSet != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Attempt to select the current beatmap on the carousel, if it is valid to be selected.
|
||||||
if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false
|
if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false
|
||||||
&& Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false))
|
&& Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom())
|
// If the current active beatmap could not be selected, select a new random beatmap.
|
||||||
|
if (!Carousel.SelectNextRandom())
|
||||||
{
|
{
|
||||||
// in the case random selection failed, we want to trigger selectionChanged
|
// in the case random selection failed, we want to trigger selectionChanged
|
||||||
// to show the dummy beatmap (we have nothing else to display).
|
// to show the dummy beatmap (we have nothing else to display).
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Users
|
|||||||
|
|
||||||
public Bindable<UserStatus> Status = new Bindable<UserStatus>();
|
public Bindable<UserStatus> Status = new Bindable<UserStatus>();
|
||||||
|
|
||||||
|
public IBindable<UserActivity> Activity = new Bindable<UserActivity>();
|
||||||
|
|
||||||
//public Team Team;
|
//public Team Team;
|
||||||
|
|
||||||
[JsonProperty(@"profile_colour")]
|
[JsonProperty(@"profile_colour")]
|
||||||
|
68
osu.Game/Users/UserActivity.cs
Normal file
68
osu.Game/Users/UserActivity.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Users
|
||||||
|
{
|
||||||
|
public abstract class UserActivity
|
||||||
|
{
|
||||||
|
public abstract string Status { get; }
|
||||||
|
public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker;
|
||||||
|
|
||||||
|
public class Modding : UserActivity
|
||||||
|
{
|
||||||
|
public override string Status => "Modding a map";
|
||||||
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChoosingBeatmap : UserActivity
|
||||||
|
{
|
||||||
|
public override string Status => "Choosing a beatmap";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MultiplayerGame : UserActivity
|
||||||
|
{
|
||||||
|
public override string Status => "Playing with others";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Editing : UserActivity
|
||||||
|
{
|
||||||
|
public BeatmapInfo Beatmap { get; }
|
||||||
|
|
||||||
|
public Editing(BeatmapInfo info)
|
||||||
|
{
|
||||||
|
Beatmap = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Status => @"Editing a beatmap";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SoloGame : UserActivity
|
||||||
|
{
|
||||||
|
public BeatmapInfo Beatmap { get; }
|
||||||
|
|
||||||
|
public Rulesets.RulesetInfo Ruleset { get; }
|
||||||
|
|
||||||
|
public SoloGame(BeatmapInfo info, Rulesets.RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
Beatmap = info;
|
||||||
|
Ruleset = ruleset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Status => @"Playing alone";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Spectating : UserActivity
|
||||||
|
{
|
||||||
|
public override string Status => @"Spectating a game";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InLobby : UserActivity
|
||||||
|
{
|
||||||
|
public override string Status => @"In a Multiplayer Lobby";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,9 @@ namespace osu.Game.Users
|
|||||||
private const float content_padding = 10;
|
private const float content_padding = 10;
|
||||||
private const float status_height = 30;
|
private const float status_height = 30;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
private Container statusBar;
|
private Container statusBar;
|
||||||
private Box statusBg;
|
private Box statusBg;
|
||||||
private OsuSpriteText statusMessage;
|
private OsuSpriteText statusMessage;
|
||||||
@ -39,6 +42,8 @@ namespace osu.Game.Users
|
|||||||
|
|
||||||
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
|
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
|
||||||
|
|
||||||
|
public readonly IBindable<UserActivity> Activity = new Bindable<UserActivity>();
|
||||||
|
|
||||||
public new Action Action;
|
public new Action Action;
|
||||||
|
|
||||||
protected Action ViewProfile;
|
protected Action ViewProfile;
|
||||||
@ -54,7 +59,7 @@ namespace osu.Game.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(OsuColour colours, UserProfileOverlay profile)
|
private void load(UserProfileOverlay profile)
|
||||||
{
|
{
|
||||||
if (colours == null)
|
if (colours == null)
|
||||||
throw new ArgumentNullException(nameof(colours));
|
throw new ArgumentNullException(nameof(colours));
|
||||||
@ -195,8 +200,8 @@ namespace osu.Game.Users
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Status.ValueChanged += status => displayStatus(status.NewValue);
|
Status.ValueChanged += status => displayStatus(status.NewValue, Activity.Value);
|
||||||
Status.ValueChanged += status => statusBg.FadeColour(status.NewValue?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint);
|
Activity.ValueChanged += activity => displayStatus(Status.Value, activity.NewValue);
|
||||||
|
|
||||||
base.Action = ViewProfile = () =>
|
base.Action = ViewProfile = () =>
|
||||||
{
|
{
|
||||||
@ -211,7 +216,7 @@ namespace osu.Game.Users
|
|||||||
Status.TriggerChange();
|
Status.TriggerChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayStatus(UserStatus status)
|
private void displayStatus(UserStatus status, UserActivity activity = null)
|
||||||
{
|
{
|
||||||
const float transition_duration = 500;
|
const float transition_duration = 500;
|
||||||
|
|
||||||
@ -226,8 +231,17 @@ namespace osu.Game.Users
|
|||||||
statusBar.ResizeHeightTo(status_height, transition_duration, Easing.OutQuint);
|
statusBar.ResizeHeightTo(status_height, transition_duration, Easing.OutQuint);
|
||||||
statusBar.FadeIn(transition_duration, Easing.OutQuint);
|
statusBar.FadeIn(transition_duration, Easing.OutQuint);
|
||||||
this.ResizeHeightTo(height, transition_duration, Easing.OutQuint);
|
this.ResizeHeightTo(height, transition_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
statusMessage.Text = status.Message;
|
if (status is UserStatusOnline && activity != null)
|
||||||
|
{
|
||||||
|
statusMessage.Text = activity.Status;
|
||||||
|
statusBg.FadeColour(activity.GetAppropriateColour(colours), 500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statusMessage.Text = status?.Message;
|
||||||
|
statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,33 +29,7 @@ namespace osu.Game.Users
|
|||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7;
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UserStatusSpectating : UserStatusOnline
|
public class UserStatusDoNotDisturb : UserStatus
|
||||||
{
|
|
||||||
public override string Message => @"Spectating a game";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserStatusInLobby : UserStatusOnline
|
|
||||||
{
|
|
||||||
public override string Message => @"in Multiplayer Lobby";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserStatusSoloGame : UserStatusBusy
|
|
||||||
{
|
|
||||||
public override string Message => @"Solo Game";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserStatusMultiplayerGame : UserStatusBusy
|
|
||||||
{
|
|
||||||
public override string Message => @"Multiplaying";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserStatusModding : UserStatusOnline
|
|
||||||
{
|
|
||||||
public override string Message => @"Modding a map";
|
|
||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserStatusDoNotDisturb : UserStatusBusy
|
|
||||||
{
|
{
|
||||||
public override string Message => @"Do not disturb";
|
public override string Message => @"Do not disturb";
|
||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark;
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark;
|
||||||
|
Loading…
Reference in New Issue
Block a user