mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 07:32:55 +08:00
Merge branch 'playlist-item-add-owner' into new-multiplayer-playlist
This commit is contained in:
commit
17bc0b51aa
@ -1050,7 +1050,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
|
private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(expected, osu.Dependencies.Get<FileStore>().QueryFiles(f => f.ReferenceCount == 1).Count());
|
Assert.AreEqual(expected, osu.Dependencies.Get<DatabaseContextFactory>().Get().FileInfo.Count(f => f.ReferenceCount == 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
|
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
|
||||||
|
@ -6,7 +6,6 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using Realms;
|
using Realms;
|
||||||
@ -18,18 +17,35 @@ namespace osu.Game.Tests.Database
|
|||||||
public class RealmLiveTests : RealmTest
|
public class RealmLiveTests : RealmTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLiveCastability()
|
public void TestLiveEquality()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
{
|
{
|
||||||
RealmLive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
|
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
|
||||||
|
|
||||||
ILive<IBeatmapInfo> iBeatmap = beatmap;
|
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive();
|
||||||
|
|
||||||
Assert.AreEqual(0, iBeatmap.Value.Length);
|
Assert.AreEqual(beatmap, beatmap2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAccessNonManaged()
|
||||||
|
{
|
||||||
|
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
|
||||||
|
var liveBeatmap = beatmap.ToLive();
|
||||||
|
|
||||||
|
Assert.IsFalse(beatmap.Hidden);
|
||||||
|
Assert.IsFalse(liveBeatmap.Value.Hidden);
|
||||||
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => liveBeatmap.PerformWrite(l => l.Hidden = true));
|
||||||
|
|
||||||
|
Assert.IsFalse(beatmap.Hidden);
|
||||||
|
Assert.IsFalse(liveBeatmap.Value.Hidden);
|
||||||
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestValueAccessWithOpenContext()
|
public void TestValueAccessWithOpenContext()
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Moq;
|
using Moq;
|
||||||
@ -13,7 +12,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using FileInfo = osu.Game.IO.FileInfo;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing.Checks
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
{
|
{
|
||||||
@ -33,14 +31,10 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
BeatmapSet = new BeatmapSetInfo
|
BeatmapSet = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
Files = new List<BeatmapSetFileInfo>(new[]
|
Files =
|
||||||
{
|
{
|
||||||
new BeatmapSetFileInfo
|
CheckTestHelpers.CreateMockFile("mp4"),
|
||||||
{
|
}
|
||||||
Filename = "abc123.mp4",
|
|
||||||
FileInfo = new FileInfo { Hash = "abcdef" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// 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.Collections.Generic;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -12,7 +12,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Checks;
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using FileInfo = osu.Game.IO.FileInfo;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing.Checks
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
{
|
{
|
||||||
@ -25,25 +24,17 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
|
var file = CheckTestHelpers.CreateMockFile("jpg");
|
||||||
|
|
||||||
check = new CheckBackgroundQuality();
|
check = new CheckBackgroundQuality();
|
||||||
beatmap = new Beatmap<HitObject>
|
beatmap = new Beatmap<HitObject>
|
||||||
{
|
{
|
||||||
BeatmapInfo = new BeatmapInfo
|
BeatmapInfo = new BeatmapInfo
|
||||||
{
|
{
|
||||||
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
|
Metadata = new BeatmapMetadata { BackgroundFile = file.Filename },
|
||||||
BeatmapSet = new BeatmapSetInfo
|
BeatmapSet = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
Files = new List<BeatmapSetFileInfo>(new[]
|
Files = { file }
|
||||||
{
|
|
||||||
new BeatmapSetFileInfo
|
|
||||||
{
|
|
||||||
Filename = "abc123.jpg",
|
|
||||||
FileInfo = new FileInfo
|
|
||||||
{
|
|
||||||
Hash = "abcdef"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -54,7 +45,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
// While this is a problem, it is out of scope for this check and is caught by a different one.
|
// While this is a problem, it is out of scope for this check and is caught by a different one.
|
||||||
beatmap.Metadata.BackgroundFile = string.Empty;
|
beatmap.Metadata.BackgroundFile = string.Empty;
|
||||||
var context = getContext(null, new MemoryStream(System.Array.Empty<byte>()));
|
var context = getContext(null, new MemoryStream(Array.Empty<byte>()));
|
||||||
|
|
||||||
Assert.That(check.Run(context), Is.Empty);
|
Assert.That(check.Run(context), Is.Empty);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// 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.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.IO;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Checks;
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -22,22 +20,17 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
|
var file = CheckTestHelpers.CreateMockFile("jpg");
|
||||||
|
|
||||||
check = new CheckBackgroundPresence();
|
check = new CheckBackgroundPresence();
|
||||||
beatmap = new Beatmap<HitObject>
|
beatmap = new Beatmap<HitObject>
|
||||||
{
|
{
|
||||||
BeatmapInfo = new BeatmapInfo
|
BeatmapInfo = new BeatmapInfo
|
||||||
{
|
{
|
||||||
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
|
Metadata = new BeatmapMetadata { BackgroundFile = file.Filename },
|
||||||
BeatmapSet = new BeatmapSetInfo
|
BeatmapSet = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
Files = new List<BeatmapSetFileInfo>(new[]
|
Files = { file }
|
||||||
{
|
|
||||||
new BeatmapSetFileInfo
|
|
||||||
{
|
|
||||||
Filename = "abc123.jpg",
|
|
||||||
FileInfo = new FileInfo { Hash = "abcdef" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
18
osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
Normal file
18
osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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.IO;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
public static class CheckTestHelpers
|
||||||
|
{
|
||||||
|
public static BeatmapSetFileInfo CreateMockFile(string extension) =>
|
||||||
|
new BeatmapSetFileInfo
|
||||||
|
{
|
||||||
|
Filename = $"abc123.{extension}",
|
||||||
|
FileInfo = new FileInfo { Hash = "abcdef" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
// 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.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using ManagedBass;
|
using ManagedBass;
|
||||||
@ -14,7 +13,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK.Audio;
|
using osuTK.Audio;
|
||||||
using FileInfo = osu.Game.IO.FileInfo;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing.Checks
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
{
|
{
|
||||||
@ -34,14 +32,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
BeatmapSet = new BeatmapSetInfo
|
BeatmapSet = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
Files = new List<BeatmapSetFileInfo>(new[]
|
Files = { CheckTestHelpers.CreateMockFile("wav") }
|
||||||
{
|
|
||||||
new BeatmapSetFileInfo
|
|
||||||
{
|
|
||||||
Filename = "abc123.wav",
|
|
||||||
FileInfo = new FileInfo { Hash = "abcdef" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -55,11 +46,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
public void TestDifferentExtension()
|
public void TestDifferentExtension()
|
||||||
{
|
{
|
||||||
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||||
beatmap.BeatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo
|
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
|
||||||
{
|
|
||||||
Filename = "abc123.jpg",
|
|
||||||
FileInfo = new FileInfo { Hash = "abcdef" }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should fail to load, but not produce an error due to the extension not being expected to load.
|
// Should fail to load, but not produce an error due to the extension not being expected to load.
|
||||||
Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
|
Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Moq;
|
using Moq;
|
||||||
@ -10,7 +9,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Checks;
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using FileInfo = osu.Game.IO.FileInfo;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing.Checks
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
{
|
{
|
||||||
@ -30,14 +28,10 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
BeatmapSet = new BeatmapSetInfo
|
BeatmapSet = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
Files = new List<BeatmapSetFileInfo>(new[]
|
Files =
|
||||||
{
|
{
|
||||||
new BeatmapSetFileInfo
|
CheckTestHelpers.CreateMockFile("jpg"),
|
||||||
{
|
}
|
||||||
Filename = "abc123.jpg",
|
|
||||||
FileInfo = new FileInfo { Hash = "abcdef" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -12,9 +12,9 @@ using osu.Game.Tests.Visual;
|
|||||||
namespace osu.Game.Tests.Online
|
namespace osu.Game.Tests.Online
|
||||||
{
|
{
|
||||||
[HeadlessTest]
|
[HeadlessTest]
|
||||||
public class TestSceneBeatmapManager : OsuTestScene
|
public class TestSceneBeatmapDownloading : OsuTestScene
|
||||||
{
|
{
|
||||||
private BeatmapManager beatmaps;
|
private BeatmapModelDownloader beatmaps;
|
||||||
private ProgressNotification recentNotification;
|
private ProgressNotification recentNotification;
|
||||||
|
|
||||||
private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo
|
private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo
|
||||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Online
|
|||||||
};
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(BeatmapManager beatmaps)
|
private void load(BeatmapModelDownloader beatmaps)
|
||||||
{
|
{
|
||||||
this.beatmaps = beatmaps;
|
this.beatmaps = beatmaps;
|
||||||
|
|
@ -33,6 +33,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets;
|
||||||
private TestBeatmapManager beatmaps;
|
private TestBeatmapManager beatmaps;
|
||||||
|
private TestBeatmapModelDownloader beatmapDownloader;
|
||||||
|
|
||||||
private string testBeatmapFile;
|
private string testBeatmapFile;
|
||||||
private BeatmapInfo testBeatmapInfo;
|
private BeatmapInfo testBeatmapInfo;
|
||||||
@ -46,6 +47,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
|
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -80,13 +82,13 @@ namespace osu.Game.Tests.Online
|
|||||||
AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet));
|
AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet));
|
||||||
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
|
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
|
||||||
|
|
||||||
AddStep("start downloading", () => beatmaps.Download(testBeatmapSet));
|
AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet));
|
||||||
addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f));
|
addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f));
|
||||||
|
|
||||||
AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f));
|
AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f));
|
||||||
addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f));
|
addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f));
|
||||||
|
|
||||||
AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile));
|
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile));
|
||||||
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
||||||
|
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
||||||
@ -171,22 +173,6 @@ namespace osu.Game.Tests.Online
|
|||||||
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
|
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> manager, IAPIProvider api, GameHost host)
|
|
||||||
{
|
|
||||||
return new TestBeatmapModelDownloader(manager, api, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
|
|
||||||
{
|
|
||||||
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider, GameHost gameHost)
|
|
||||||
: base(importer, apiProvider, gameHost)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize)
|
|
||||||
=> new TestDownloadRequest(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TestBeatmapModelManager : BeatmapModelManager
|
internal class TestBeatmapModelManager : BeatmapModelManager
|
||||||
{
|
{
|
||||||
private readonly TestBeatmapManager testBeatmapManager;
|
private readonly TestBeatmapManager testBeatmapManager;
|
||||||
@ -205,6 +191,17 @@ namespace osu.Game.Tests.Online
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
|
||||||
|
{
|
||||||
|
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider, GameHost gameHost)
|
||||||
|
: base(importer, apiProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize)
|
||||||
|
=> new TestDownloadRequest(set);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDownloadRequest : ArchiveDownloadRequest<IBeatmapSetInfo>
|
private class TestDownloadRequest : ArchiveDownloadRequest<IBeatmapSetInfo>
|
||||||
{
|
{
|
||||||
public new void SetProgress(float progress) => base.SetProgress(progress);
|
public new void SetProgress(float progress) => base.SetProgress(progress);
|
||||||
|
@ -23,6 +23,11 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
{
|
{
|
||||||
public class TestSceneBeatmapCard : OsuTestScene
|
public class TestSceneBeatmapCard : OsuTestScene
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources.
|
||||||
|
/// </summary>
|
||||||
|
private const int online_id = 163112;
|
||||||
|
|
||||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
private APIBeatmapSet[] testCases;
|
private APIBeatmapSet[] testCases;
|
||||||
@ -38,7 +43,6 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
var normal = CreateAPIBeatmapSet(Ruleset.Value);
|
var normal = CreateAPIBeatmapSet(Ruleset.Value);
|
||||||
normal.HasVideo = true;
|
normal.HasVideo = true;
|
||||||
normal.HasStoryboard = true;
|
normal.HasStoryboard = true;
|
||||||
normal.OnlineID = 241526;
|
|
||||||
|
|
||||||
var withStatistics = CreateAPIBeatmapSet(Ruleset.Value);
|
var withStatistics = CreateAPIBeatmapSet(Ruleset.Value);
|
||||||
withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats";
|
withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats";
|
||||||
@ -106,6 +110,9 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
explicitFeaturedMap,
|
explicitFeaturedMap,
|
||||||
longName
|
longName
|
||||||
};
|
};
|
||||||
|
|
||||||
|
foreach (var testCase in testCases)
|
||||||
|
testCase.OnlineID = online_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
|
private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
|
||||||
@ -191,9 +198,9 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
private void ensureSoleilyRemoved()
|
private void ensureSoleilyRemoved()
|
||||||
{
|
{
|
||||||
AddUntilStep("ensure manager loaded", () => beatmaps != null);
|
AddUntilStep("ensure manager loaded", () => beatmaps != null);
|
||||||
AddStep("remove soleily", () =>
|
AddStep("remove map", () =>
|
||||||
{
|
{
|
||||||
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526);
|
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == online_id);
|
||||||
|
|
||||||
if (beatmap != null) beatmaps.Delete(beatmap);
|
if (beatmap != null) beatmaps.Delete(beatmap);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Beatmaps
|
||||||
|
{
|
||||||
|
public class TestSceneBeatmapCardThumbnail : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private PlayButton playButton => this.ChildrenOfType<PlayButton>().Single();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestThumbnailPreview()
|
||||||
|
{
|
||||||
|
BeatmapCardThumbnail thumbnail = null;
|
||||||
|
|
||||||
|
AddStep("create thumbnail", () =>
|
||||||
|
{
|
||||||
|
var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value);
|
||||||
|
beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online.
|
||||||
|
|
||||||
|
Child = thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(200)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
|
||||||
|
AddUntilStep("button visible", () => playButton.IsPresent);
|
||||||
|
|
||||||
|
AddStep("click button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(playButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value);
|
||||||
|
iconIs(FontAwesome.Solid.Stop);
|
||||||
|
|
||||||
|
AddStep("click again", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(playButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for stop", () => !playButton.Playing.Value && playButton.Enabled.Value);
|
||||||
|
iconIs(FontAwesome.Solid.Play);
|
||||||
|
|
||||||
|
AddStep("click again", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(playButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value);
|
||||||
|
iconIs(FontAwesome.Solid.Stop);
|
||||||
|
|
||||||
|
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
|
||||||
|
AddWaitStep("wait some", 3);
|
||||||
|
AddAssert("button still visible", () => playButton.IsPresent);
|
||||||
|
|
||||||
|
// The track plays in real-time, so we need to check for progress in increments to avoid timeout.
|
||||||
|
AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.25);
|
||||||
|
AddUntilStep("progress > 0.5", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.5);
|
||||||
|
AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.75);
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
|
||||||
|
AddUntilStep("button hidden", () => !playButton.IsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));
|
||||||
|
}
|
||||||
|
}
|
@ -69,7 +69,10 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
|
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
Screens.Select.SongSelect songSelect = null;
|
||||||
|
|
||||||
|
PushAndConfirm(() => songSelect = new PlaySongSelect());
|
||||||
|
AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
||||||
AddStep("Open options", () => InputManager.Key(Key.F3));
|
AddStep("Open options", () => InputManager.Key(Key.F3));
|
||||||
|
@ -78,6 +78,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCorrectItemSelectedAfterNewItemAdded()
|
||||||
|
{
|
||||||
|
addItem(() => OtherBeatmap);
|
||||||
|
AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
|
||||||
|
}
|
||||||
|
|
||||||
private void addItem(Func<BeatmapInfo> beatmap)
|
private void addItem(Func<BeatmapInfo> beatmap)
|
||||||
{
|
{
|
||||||
AddStep("click edit button", () =>
|
AddStep("click edit button", () =>
|
||||||
@ -86,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded);
|
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
|
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
|
||||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -22,6 +23,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay;
|
using osu.Game.Screens.OnlinePlay;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -34,6 +36,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private BeatmapManager manager;
|
private BeatmapManager manager;
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
|
[Cached(typeof(UserLookupCache))]
|
||||||
|
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -304,6 +309,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(false)]
|
||||||
|
[TestCase(true)]
|
||||||
|
public void TestWithOwner(bool withOwner)
|
||||||
|
{
|
||||||
|
createPlaylist(false, false, withOwner);
|
||||||
|
|
||||||
|
AddAssert("owner visible", () => playlist.ChildrenOfType<UpdateableAvatar>().All(a => a.IsPresent == withOwner));
|
||||||
|
}
|
||||||
|
|
||||||
private void moveToItem(int index, Vector2? offset = null)
|
private void moveToItem(int index, Vector2? offset = null)
|
||||||
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
|
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
|
||||||
|
|
||||||
@ -327,11 +341,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||||
|
|
||||||
private void createPlaylist(bool allowEdit, bool allowSelection)
|
private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false)
|
||||||
{
|
{
|
||||||
AddStep("create playlist", () =>
|
AddStep("create playlist", () =>
|
||||||
{
|
{
|
||||||
Child = playlist = new TestPlaylist(allowEdit, allowSelection)
|
Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -343,6 +357,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
playlist.Items.Add(new PlaylistItem
|
playlist.Items.Add(new PlaylistItem
|
||||||
{
|
{
|
||||||
ID = i,
|
ID = i,
|
||||||
|
OwnerID = 2,
|
||||||
Beatmap =
|
Beatmap =
|
||||||
{
|
{
|
||||||
Value = i % 2 == 1
|
Value = i % 2 == 1
|
||||||
@ -390,6 +405,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
playlist.Items.Add(new PlaylistItem
|
playlist.Items.Add(new PlaylistItem
|
||||||
{
|
{
|
||||||
ID = index++,
|
ID = index++,
|
||||||
|
OwnerID = 2,
|
||||||
Beatmap = { Value = b },
|
Beatmap = { Value = b },
|
||||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
RequiredMods =
|
RequiredMods =
|
||||||
@ -409,8 +425,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
||||||
|
|
||||||
public TestPlaylist(bool allowEdit, bool allowSelection)
|
public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false)
|
||||||
: base(allowEdit, allowSelection)
|
: base(allowEdit, allowSelection, showItemOwner: showItemOwner)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded);
|
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||||
|
|
||||||
BeatmapInfo otherBeatmap = null;
|
BeatmapInfo otherBeatmap = null;
|
||||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
||||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
// edit playlist item
|
// edit playlist item
|
||||||
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||||
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault() != null);
|
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||||
|
|
||||||
// select beatmap
|
// select beatmap
|
||||||
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||||
@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, API.LocalUser.Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
@ -283,7 +283,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, API.LocalUser.Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
@ -336,7 +336,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, API.LocalUser.Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
@ -597,7 +597,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, API.LocalUser.Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value)));
|
AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value)));
|
||||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value)));
|
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value)));
|
||||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -66,7 +66,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
Player player = null;
|
Player player = null;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestPlaySongSelect());
|
Screens.Select.SongSelect songSelect = null;
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
|
||||||
@ -98,7 +100,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestPlaySongSelect());
|
Screens.Select.SongSelect songSelect = null;
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
|
||||||
@ -130,7 +134,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestPlaySongSelect());
|
Screens.Select.SongSelect songSelect = null;
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
||||||
|
|
||||||
|
@ -572,7 +572,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddStep("add mixed ruleset beatmapset", () =>
|
AddStep("add mixed ruleset beatmapset", () =>
|
||||||
{
|
{
|
||||||
testMixed = TestResources.CreateTestBeatmapSetInfo();
|
testMixed = TestResources.CreateTestBeatmapSetInfo(3);
|
||||||
|
|
||||||
for (int i = 0; i <= 2; i++)
|
for (int i = 0; i <= 2; i++)
|
||||||
{
|
{
|
||||||
@ -595,7 +595,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
BeatmapSetInfo testSingle = null;
|
BeatmapSetInfo testSingle = null;
|
||||||
AddStep("add single ruleset beatmapset", () =>
|
AddStep("add single ruleset beatmapset", () =>
|
||||||
{
|
{
|
||||||
testSingle = TestResources.CreateTestBeatmapSetInfo();
|
testSingle = TestResources.CreateTestBeatmapSetInfo(3);
|
||||||
testSingle.Beatmaps.ForEach(b =>
|
testSingle.Beatmaps.ForEach(b =>
|
||||||
{
|
{
|
||||||
b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
|
b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
|
||||||
@ -615,7 +615,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
List<BeatmapSetInfo> manySets = new List<BeatmapSetInfo>();
|
List<BeatmapSetInfo> manySets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
for (int i = 1; i <= 50; i++)
|
for (int i = 1; i <= 50; i++)
|
||||||
manySets.Add(TestResources.CreateTestBeatmapSetInfo(i));
|
manySets.Add(TestResources.CreateTestBeatmapSetInfo(3));
|
||||||
|
|
||||||
loadBeatmaps(manySets);
|
loadBeatmaps(manySets);
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
|
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler));
|
||||||
|
|
||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
|
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler));
|
||||||
|
|
||||||
beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0];
|
beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0];
|
||||||
|
|
||||||
|
@ -29,12 +29,11 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Handles general operations related to global beatmap management.
|
/// Handles general operations related to global beatmap management.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
public class BeatmapManager : IModelDownloader<IBeatmapSetInfo>, IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, BeatmapSetFileInfo>, IModelImporter<BeatmapSetInfo>, IWorkingBeatmapCache, IDisposable
|
public class BeatmapManager : IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, BeatmapSetFileInfo>, IModelImporter<BeatmapSetInfo>, IWorkingBeatmapCache, IDisposable
|
||||||
{
|
{
|
||||||
public ITrackStore BeatmapTrackStore { get; }
|
public ITrackStore BeatmapTrackStore { get; }
|
||||||
|
|
||||||
private readonly BeatmapModelManager beatmapModelManager;
|
private readonly BeatmapModelManager beatmapModelManager;
|
||||||
private readonly BeatmapModelDownloader beatmapModelDownloader;
|
|
||||||
|
|
||||||
private readonly WorkingBeatmapCache workingBeatmapCache;
|
private readonly WorkingBeatmapCache workingBeatmapCache;
|
||||||
private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue;
|
private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue;
|
||||||
@ -46,7 +45,6 @@ namespace osu.Game.Beatmaps
|
|||||||
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
||||||
|
|
||||||
beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host);
|
beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host);
|
||||||
beatmapModelDownloader = CreateBeatmapModelDownloader(beatmapModelManager, api, host);
|
|
||||||
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
||||||
|
|
||||||
workingBeatmapCache.BeatmapManager = beatmapModelManager;
|
workingBeatmapCache.BeatmapManager = beatmapModelManager;
|
||||||
@ -59,11 +57,6 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> modelManager, IAPIProvider api, GameHost host)
|
|
||||||
{
|
|
||||||
return new BeatmapModelDownloader(modelManager, api, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap defaultBeatmap, GameHost host)
|
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap defaultBeatmap, GameHost host)
|
||||||
{
|
{
|
||||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||||
@ -185,11 +178,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<Notification> PostNotification
|
public Action<Notification> PostNotification
|
||||||
{
|
{
|
||||||
set
|
set => beatmapModelManager.PostNotification = value;
|
||||||
{
|
|
||||||
beatmapModelManager.PostNotification = value;
|
|
||||||
beatmapModelDownloader.PostNotification = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -225,21 +214,6 @@ namespace osu.Game.Beatmaps
|
|||||||
remove => beatmapModelManager.ItemRemoved -= value;
|
remove => beatmapModelManager.ItemRemoved -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ImportFromStableAsync(StableStorage stableStorage)
|
|
||||||
{
|
|
||||||
return beatmapModelManager.ImportFromStableAsync(stableStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Export(BeatmapSetInfo item)
|
|
||||||
{
|
|
||||||
beatmapModelManager.Export(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExportModelTo(BeatmapSetInfo model, Stream outputStream)
|
|
||||||
{
|
|
||||||
beatmapModelManager.ExportModelTo(model, outputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(BeatmapSetInfo item)
|
public void Update(BeatmapSetInfo item)
|
||||||
{
|
{
|
||||||
beatmapModelManager.Update(item);
|
beatmapModelManager.Update(item);
|
||||||
@ -267,28 +241,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Implementation of IModelDownloader<BeatmapSetInfo>
|
|
||||||
|
|
||||||
public event Action<ArchiveDownloadRequest<IBeatmapSetInfo>> DownloadBegan
|
|
||||||
{
|
|
||||||
add => beatmapModelDownloader.DownloadBegan += value;
|
|
||||||
remove => beatmapModelDownloader.DownloadBegan -= value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<ArchiveDownloadRequest<IBeatmapSetInfo>> DownloadFailed
|
|
||||||
{
|
|
||||||
add => beatmapModelDownloader.DownloadFailed += value;
|
|
||||||
remove => beatmapModelDownloader.DownloadFailed -= value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false) =>
|
|
||||||
beatmapModelDownloader.Download(model, minimiseDownloadSize);
|
|
||||||
|
|
||||||
public ArchiveDownloadRequest<IBeatmapSetInfo> GetExistingDownload(IBeatmapSetInfo model) =>
|
|
||||||
beatmapModelDownloader.GetExistingDownload(model);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementation of ICanAcceptFiles
|
#region Implementation of ICanAcceptFiles
|
||||||
|
|
||||||
public Task Import(params string[] paths)
|
public Task Import(params string[] paths)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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.Platform;
|
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
@ -16,8 +15,8 @@ namespace osu.Game.Beatmaps
|
|||||||
public override ArchiveDownloadRequest<IBeatmapSetInfo> GetExistingDownload(IBeatmapSetInfo model)
|
public override ArchiveDownloadRequest<IBeatmapSetInfo> GetExistingDownload(IBeatmapSetInfo model)
|
||||||
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID);
|
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID);
|
||||||
|
|
||||||
public BeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api, GameHost host = null)
|
public BeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api)
|
||||||
: base(beatmapImporter, api, host)
|
: base(beatmapImporter, api)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Handles ef-core storage of beatmaps.
|
/// Handles ef-core storage of beatmaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
public class BeatmapModelManager : ArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>, IBeatmapModelManager
|
public class BeatmapModelManager : ArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when a single difficulty has been hidden.
|
/// Fired when a single difficulty has been hidden.
|
||||||
@ -58,10 +58,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osu" };
|
protected override string[] HashableFileTypes => new[] { ".osu" };
|
||||||
|
|
||||||
protected override string ImportFromStablePath => ".";
|
|
||||||
|
|
||||||
protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage();
|
|
||||||
|
|
||||||
private readonly BeatmapStore beatmaps;
|
private readonly BeatmapStore beatmaps;
|
||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
|
|
||||||
@ -216,7 +212,7 @@ namespace osu.Game.Beatmaps
|
|||||||
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
|
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
|
||||||
|
|
||||||
// metadata may have changed; update the path with the standard format.
|
// metadata may have changed; update the path with the standard format.
|
||||||
beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu");
|
beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename();
|
||||||
|
|
||||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||||
|
|
||||||
|
@ -36,8 +36,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None;
|
public BeatmapOnlineStatus Status { get; set; } = BeatmapOnlineStatus.None;
|
||||||
|
|
||||||
[NotNull]
|
public List<BeatmapSetFileInfo> Files { get; } = new List<BeatmapSetFileInfo>();
|
||||||
public List<BeatmapSetFileInfo> Files { get; set; } = new List<BeatmapSetFileInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum star difficulty of all beatmaps in this set.
|
/// The maximum star difficulty of all beatmaps in this set.
|
||||||
@ -96,7 +95,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata ?? Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata();
|
IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata ?? Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata();
|
||||||
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
|
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
|
||||||
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;
|
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => Files;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -23,7 +25,6 @@ using osu.Game.Overlays.BeatmapSet;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK.Graphics;
|
|
||||||
using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton;
|
using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
@ -42,27 +43,23 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
private readonly BeatmapDownloadTracker downloadTracker;
|
private readonly BeatmapDownloadTracker downloadTracker;
|
||||||
|
|
||||||
private UpdateableOnlineBeatmapSetCover leftCover;
|
private BeatmapCardThumbnail thumbnail = null!;
|
||||||
private FillFlowContainer leftIconArea;
|
|
||||||
|
|
||||||
private Container rightAreaBackground;
|
private Container rightAreaBackground = null!;
|
||||||
private Container<BeatmapCardIconButton> rightAreaButtons;
|
private Container<BeatmapCardIconButton> rightAreaButtons = null!;
|
||||||
|
|
||||||
private Container mainContent;
|
private Container mainContent = null!;
|
||||||
private BeatmapCardContentBackground mainContentBackground;
|
private BeatmapCardContentBackground mainContentBackground = null!;
|
||||||
|
private FillFlowContainer<BeatmapCardStatistic> statisticsContainer = null!;
|
||||||
|
|
||||||
private GridContainer titleContainer;
|
private FillFlowContainer idleBottomContent = null!;
|
||||||
private GridContainer artistContainer;
|
private BeatmapCardDownloadProgressBar downloadProgressBar = null!;
|
||||||
private FillFlowContainer<BeatmapCardStatistic> statisticsContainer;
|
|
||||||
|
|
||||||
private FillFlowContainer idleBottomContent;
|
|
||||||
private BeatmapCardDownloadProgressBar downloadProgressBar;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider colourProvider { get; set; }
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
public BeatmapCard(APIBeatmapSet beatmapSet)
|
public BeatmapCard(APIBeatmapSet beatmapSet)
|
||||||
: base(HoverSampleSet.Submit)
|
: base(HoverSampleSet.Submit)
|
||||||
@ -72,14 +69,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
downloadTracker = new BeatmapDownloadTracker(beatmapSet);
|
downloadTracker = new BeatmapDownloadTracker(beatmapSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load()
|
private void load(BeatmapSetOverlay? beatmapSetOverlay)
|
||||||
{
|
{
|
||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
CornerRadius = corner_radius;
|
CornerRadius = corner_radius;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
|
FillFlowContainer leftIconArea;
|
||||||
|
GridContainer titleContainer;
|
||||||
|
GridContainer artistContainer;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
downloadTracker,
|
downloadTracker,
|
||||||
@ -98,24 +99,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
Colour = Colour4.White
|
Colour = Colour4.White
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new Container
|
thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
||||||
{
|
{
|
||||||
Name = @"Left (icon) area",
|
Name = @"Left (icon) area",
|
||||||
Size = new Vector2(height),
|
Size = new Vector2(height),
|
||||||
Children = new Drawable[]
|
Padding = new MarginPadding { Right = corner_radius },
|
||||||
|
Child = leftIconArea = new FillFlowContainer
|
||||||
{
|
{
|
||||||
leftCover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
Margin = new MarginPadding(5),
|
||||||
{
|
AutoSizeAxes = Axes.Both,
|
||||||
RelativeSizeAxes = Axes.Both,
|
Direction = FillDirection.Horizontal,
|
||||||
OnlineInfo = beatmapSet
|
Spacing = new Vector2(1)
|
||||||
},
|
|
||||||
leftIconArea = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding(5),
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
@ -319,10 +313,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (beatmapSet.HasVideo)
|
if (beatmapSet.HasVideo)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film));
|
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (beatmapSet.HasStoryboard)
|
if (beatmapSet.HasStoryboard)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image));
|
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (beatmapSet.HasExplicitContent)
|
if (beatmapSet.HasExplicitContent)
|
||||||
{
|
{
|
||||||
@ -343,6 +337,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
Margin = new MarginPadding { Left = 5 }
|
Margin = new MarginPadding { Left = 5 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -395,10 +391,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
if (IsHovered)
|
if (IsHovered)
|
||||||
targetWidth = targetWidth - icon_area_width + corner_radius;
|
targetWidth = targetWidth - icon_area_width + corner_radius;
|
||||||
|
|
||||||
|
thumbnail.Dimmed.Value = IsHovered;
|
||||||
|
|
||||||
mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint);
|
mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint);
|
||||||
mainContentBackground.Dimmed.Value = IsHovered;
|
mainContentBackground.Dimmed.Value = IsHovered;
|
||||||
|
|
||||||
leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint);
|
rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
95
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs
Normal file
95
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// 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.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
|
{
|
||||||
|
public class BeatmapCardThumbnail : Container
|
||||||
|
{
|
||||||
|
public BindableBool Dimmed { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public new MarginPadding Padding
|
||||||
|
{
|
||||||
|
get => foreground.Padding;
|
||||||
|
set => foreground.Padding = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly UpdateableOnlineBeatmapSetCover cover;
|
||||||
|
private readonly Container foreground;
|
||||||
|
private readonly PlayButton playButton;
|
||||||
|
private readonly SmoothCircularProgress progress;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
OnlineInfo = beatmapSetInfo
|
||||||
|
},
|
||||||
|
foreground = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
playButton = new PlayButton(beatmapSetInfo)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
progress = new SmoothCircularProgress
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(50),
|
||||||
|
InnerRadius = 0.2f
|
||||||
|
},
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
progress.Colour = colourProvider.Highlight1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Dimmed.BindValueChanged(_ => updateState());
|
||||||
|
|
||||||
|
playButton.Playing.BindValueChanged(_ => updateState(), true);
|
||||||
|
((IBindable<double>)progress.Current).BindTo(playButton.Progress);
|
||||||
|
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
bool shouldDim = Dimmed.Value || playButton.Playing.Value;
|
||||||
|
|
||||||
|
playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
|
|
||||||
protected readonly SpriteIcon Icon;
|
protected readonly SpriteIcon Icon;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
protected BeatmapCardIconButton()
|
protected BeatmapCardIconButton()
|
||||||
@ -61,7 +63,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
|
|
||||||
Child = content = new Container
|
base.Content.Add(content = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
@ -75,7 +77,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
Anchor = Anchor.Centre
|
Anchor = Anchor.Centre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
Size = new Vector2(24);
|
Size = new Vector2(24);
|
||||||
IconSize = 12;
|
IconSize = 12;
|
||||||
|
@ -3,14 +3,17 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
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.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||||
{
|
{
|
||||||
@ -23,13 +26,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
|
|
||||||
private Bindable<bool> preferNoVideo = null!;
|
private Bindable<bool> preferNoVideo = null!;
|
||||||
|
|
||||||
|
private readonly LoadingSpinner spinner;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; } = null!;
|
private BeatmapModelDownloader beatmaps { get; set; } = null!;
|
||||||
|
|
||||||
public DownloadButton(APIBeatmapSet beatmapSet)
|
public DownloadButton(APIBeatmapSet beatmapSet)
|
||||||
{
|
{
|
||||||
Icon.Icon = FontAwesome.Solid.Download;
|
Icon.Icon = FontAwesome.Solid.Download;
|
||||||
|
|
||||||
|
Content.Add(spinner = new LoadingSpinner { Size = new Vector2(IconSize) });
|
||||||
|
|
||||||
this.beatmapSet = beatmapSet;
|
this.beatmapSet = beatmapSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,21 +56,44 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
this.FadeTo(state.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
switch (state.Value)
|
||||||
|
|
||||||
if (beatmapSet.Availability.DownloadDisabled)
|
|
||||||
{
|
{
|
||||||
Enabled.Value = false;
|
case DownloadState.Downloading:
|
||||||
TooltipText = BeatmapsetsStrings.AvailabilityDisabled;
|
case DownloadState.Importing:
|
||||||
return;
|
Action = null;
|
||||||
|
TooltipText = string.Empty;
|
||||||
|
spinner.Show();
|
||||||
|
Icon.Hide();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.LocallyAvailable:
|
||||||
|
Action = null;
|
||||||
|
TooltipText = string.Empty;
|
||||||
|
this.FadeOut(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.NotDownloaded:
|
||||||
|
if (beatmapSet.Availability.DownloadDisabled)
|
||||||
|
{
|
||||||
|
Enabled.Value = false;
|
||||||
|
TooltipText = BeatmapsetsStrings.AvailabilityDisabled;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value);
|
||||||
|
this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
spinner.Hide();
|
||||||
|
Icon.Show();
|
||||||
|
|
||||||
|
if (!beatmapSet.HasVideo)
|
||||||
|
TooltipText = BeatmapsetsStrings.PanelDownloadAll;
|
||||||
|
else
|
||||||
|
TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Unknown {nameof(DownloadState)} specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!beatmapSet.HasVideo)
|
|
||||||
TooltipText = BeatmapsetsStrings.PanelDownloadAll;
|
|
||||||
else
|
|
||||||
TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo;
|
|
||||||
|
|
||||||
Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
142
osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs
Normal file
142
osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||||
|
{
|
||||||
|
public class PlayButton : OsuHoverContainer
|
||||||
|
{
|
||||||
|
public IBindable<double> Progress => progress;
|
||||||
|
private readonly BindableDouble progress = new BindableDouble();
|
||||||
|
|
||||||
|
public BindableBool Playing { get; } = new BindableBool();
|
||||||
|
|
||||||
|
private readonly IBeatmapSetInfo beatmapSetInfo;
|
||||||
|
|
||||||
|
protected override IEnumerable<Drawable> EffectTargets => icon.Yield();
|
||||||
|
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
private readonly LoadingSpinner loadingSpinner;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private PreviewTrackManager previewTrackManager { get; set; } = null!;
|
||||||
|
|
||||||
|
private PreviewTrack? previewTrack;
|
||||||
|
|
||||||
|
public PlayButton(IBeatmapSetInfo beatmapSetInfo)
|
||||||
|
{
|
||||||
|
this.beatmapSetInfo = beatmapSetInfo;
|
||||||
|
|
||||||
|
Anchor = Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Icon = FontAwesome.Solid.Play,
|
||||||
|
Size = new Vector2(14)
|
||||||
|
},
|
||||||
|
loadingSpinner = new LoadingSpinner
|
||||||
|
{
|
||||||
|
Size = new Vector2(14)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Action = () => Playing.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
HoverColour = colours.Yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Playing.BindValueChanged(updateState, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded)
|
||||||
|
progress.Value = previewTrack.CurrentTime / previewTrack.Length;
|
||||||
|
else
|
||||||
|
progress.Value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(ValueChangedEvent<bool> playing)
|
||||||
|
{
|
||||||
|
icon.Icon = playing.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play;
|
||||||
|
|
||||||
|
if (!playing.NewValue)
|
||||||
|
{
|
||||||
|
stopPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previewTrack == null)
|
||||||
|
{
|
||||||
|
toggleLoading(true);
|
||||||
|
LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tryStartPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopPreview()
|
||||||
|
{
|
||||||
|
toggleLoading(false);
|
||||||
|
Playing.Value = false;
|
||||||
|
previewTrack?.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPreviewLoaded(PreviewTrack loadedPreview)
|
||||||
|
{
|
||||||
|
// another async load might have completed before this one.
|
||||||
|
// if so, do not make any changes.
|
||||||
|
if (loadedPreview != previewTrack)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddInternal(loadedPreview);
|
||||||
|
toggleLoading(false);
|
||||||
|
|
||||||
|
loadedPreview.Stopped += () => Schedule(() => Playing.Value = false);
|
||||||
|
|
||||||
|
if (Playing.Value)
|
||||||
|
tryStartPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryStartPreview()
|
||||||
|
{
|
||||||
|
if (previewTrack?.Start() == false)
|
||||||
|
Playing.Value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleLoading(bool loading)
|
||||||
|
{
|
||||||
|
Enabled.Value = !loading;
|
||||||
|
icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
// 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.Database;
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
|
||||||
{
|
|
||||||
public interface IBeatmapModelManager : IModelManager<BeatmapSetInfo>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provide an online lookup queue component to handle populating online beatmap metadata.
|
|
||||||
/// </summary>
|
|
||||||
BeatmapOnlineLookupQueue OnlineLookupQueue { set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provide a working beatmap cache, used to invalidate entries on changes.
|
|
||||||
/// </summary>
|
|
||||||
IWorkingBeatmapCache WorkingBeatmapCache { set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
|
/// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBeatmapSetInfo : IHasOnlineID<int>, IEquatable<IBeatmapSetInfo>
|
public interface IBeatmapSetInfo : IHasOnlineID<int>, IEquatable<IBeatmapSetInfo>, IHasNamedFiles
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The date when this beatmap was imported.
|
/// The date when this beatmap was imported.
|
||||||
@ -29,11 +29,6 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IEnumerable<IBeatmapInfo> Beatmaps { get; }
|
IEnumerable<IBeatmapInfo> Beatmaps { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All files used by this set.
|
|
||||||
/// </summary>
|
|
||||||
IEnumerable<INamedFileUsage> Files { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum star difficulty of all beatmaps in this set.
|
/// The maximum star difficulty of all beatmaps in this set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -20,7 +20,6 @@ using osu.Game.IO;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using SharpCompress.Archives.Zip;
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
@ -82,8 +81,6 @@ namespace osu.Game.Database
|
|||||||
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
|
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
|
||||||
private ArchiveImportIPCChannel ipc;
|
private ArchiveImportIPCChannel ipc;
|
||||||
|
|
||||||
private readonly Storage exportStorage;
|
|
||||||
|
|
||||||
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
|
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
|
||||||
{
|
{
|
||||||
ContextFactory = contextFactory;
|
ContextFactory = contextFactory;
|
||||||
@ -92,8 +89,6 @@ namespace osu.Game.Database
|
|||||||
ModelStore.ItemUpdated += item => handleEvent(() => ItemUpdated?.Invoke(item));
|
ModelStore.ItemUpdated += item => handleEvent(() => ItemUpdated?.Invoke(item));
|
||||||
ModelStore.ItemRemoved += item => handleEvent(() => ItemRemoved?.Invoke(item));
|
ModelStore.ItemRemoved += item => handleEvent(() => ItemRemoved?.Invoke(item));
|
||||||
|
|
||||||
exportStorage = storage.GetStorageForDirectory(@"exports");
|
|
||||||
|
|
||||||
Files = new FileStore(contextFactory, storage);
|
Files = new FileStore(contextFactory, storage);
|
||||||
|
|
||||||
if (importHost != null)
|
if (importHost != null)
|
||||||
@ -392,7 +387,8 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
LogForModel(item, @"Beginning import...");
|
LogForModel(item, @"Beginning import...");
|
||||||
|
|
||||||
item.Files = archive != null ? createFileInfos(archive, Files) : new List<TFileModel>();
|
if (archive != null)
|
||||||
|
item.Files.AddRange(createFileInfos(archive, Files));
|
||||||
item.Hash = ComputeHash(item);
|
item.Hash = ComputeHash(item);
|
||||||
|
|
||||||
await Populate(item, archive, cancellationToken).ConfigureAwait(false);
|
await Populate(item, archive, cancellationToken).ConfigureAwait(false);
|
||||||
@ -451,41 +447,6 @@ namespace osu.Game.Database
|
|||||||
return item.ToEntityFrameworkLive();
|
return item.ToEntityFrameworkLive();
|
||||||
}, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false);
|
}, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exports an item to a legacy (.zip based) package.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to export.</param>
|
|
||||||
public void Export(TModel item)
|
|
||||||
{
|
|
||||||
var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID);
|
|
||||||
|
|
||||||
if (retrievedItem == null)
|
|
||||||
throw new ArgumentException(@"Specified model could not be found", nameof(item));
|
|
||||||
|
|
||||||
string filename = $"{GetValidFilename(item.ToString())}{HandledExtensions.First()}";
|
|
||||||
|
|
||||||
using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create))
|
|
||||||
ExportModelTo(retrievedItem, stream);
|
|
||||||
|
|
||||||
exportStorage.PresentFileExternally(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exports an item to the given output stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="model">The item to export.</param>
|
|
||||||
/// <param name="outputStream">The output stream to export to.</param>
|
|
||||||
public virtual void ExportModelTo(TModel model, Stream outputStream)
|
|
||||||
{
|
|
||||||
using (var archive = ZipArchive.Create())
|
|
||||||
{
|
|
||||||
foreach (var file in model.Files)
|
|
||||||
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.GetStoragePath()));
|
|
||||||
|
|
||||||
archive.SaveTo(outputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replace an existing file with a new version.
|
/// Replace an existing file with a new version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -727,17 +688,6 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
#region osu-stable import
|
#region osu-stable import
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The relative path from osu-stable's data directory to import items from.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual string ImportFromStablePath => null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in <see cref="ImportFromStablePath"/>.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual IEnumerable<string> GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath)
|
|
||||||
.Select(path => storage.GetFullPath(path));
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this specified path should be removed after successful import.
|
/// Whether this specified path should be removed after successful import.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -745,29 +695,6 @@ namespace osu.Game.Database
|
|||||||
/// <returns>Whether to perform deletion.</returns>
|
/// <returns>Whether to perform deletion.</returns>
|
||||||
protected virtual bool ShouldDeleteArchive(string path) => false;
|
protected virtual bool ShouldDeleteArchive(string path) => false;
|
||||||
|
|
||||||
public Task ImportFromStableAsync(StableStorage stableStorage)
|
|
||||||
{
|
|
||||||
var storage = PrepareStableStorage(stableStorage);
|
|
||||||
|
|
||||||
// Handle situations like when the user does not have a Skins folder.
|
|
||||||
if (!storage.ExistsDirectory(ImportFromStablePath))
|
|
||||||
{
|
|
||||||
string fullPath = storage.GetFullPath(ImportFromStablePath);
|
|
||||||
|
|
||||||
Logger.Log(@$"Folder ""{fullPath}"" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray()).ConfigureAwait(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run any required traversal operations on the stable storage location before performing operations.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stableStorage">The stable storage.</param>
|
|
||||||
/// <returns>The usable storage. Return the unchanged <paramref name="stableStorage"/> if no traversal is required.</returns>
|
|
||||||
protected virtual Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -908,18 +835,5 @@ namespace osu.Game.Database
|
|||||||
// this doesn't follow the SHA2 hashing schema intentionally, so such entries on the data store can be identified.
|
// this doesn't follow the SHA2 hashing schema intentionally, so such entries on the data store can be identified.
|
||||||
return Guid.NewGuid().ToString();
|
return Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars()
|
|
||||||
// Backslash is added to avoid issues when exporting to zip.
|
|
||||||
// See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143.
|
|
||||||
.Append('\\')
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
protected string GetValidFilename(string filename)
|
|
||||||
{
|
|
||||||
foreach (char c in invalidFilenameCharacters)
|
|
||||||
filename = filename.Replace(c, '_');
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="obj">The object to use as a reference when negotiating a local instance.</param>
|
/// <param name="obj">The object to use as a reference when negotiating a local instance.</param>
|
||||||
/// <param name="lookupSource">An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes.</param>
|
/// <param name="lookupSource">An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes.</param>
|
||||||
/// <typeparam name="T">A valid EF-stored type.</typeparam>
|
/// <typeparam name="T">A valid EF-stored type.</typeparam>
|
||||||
protected virtual void Refresh<T>(ref T obj, IQueryable<T> lookupSource = null) where T : class, IHasPrimaryKey
|
protected void Refresh<T>(ref T obj, IQueryable<T> lookupSource = null) where T : class, IHasPrimaryKey
|
||||||
{
|
{
|
||||||
using (var usage = ContextFactory.GetForWrite())
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
public class EntityFrameworkLive<T> : ILive<T> where T : class
|
public class EntityFrameworkLive<T> : ILive<T> where T : class
|
||||||
{
|
{
|
||||||
public EntityFrameworkLive(T item)
|
public EntityFrameworkLive(T item)
|
||||||
{
|
{
|
||||||
|
IsManaged = true; // no way to really know.
|
||||||
Value = item;
|
Value = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +32,10 @@ namespace osu.Game.Database
|
|||||||
perform(Value);
|
perform(Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsManaged { get; }
|
||||||
|
|
||||||
public T Value { get; }
|
public T Value { get; }
|
||||||
|
|
||||||
|
public bool Equals(ILive<T>? other) => ID == other?.ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
@ -12,7 +13,8 @@ namespace osu.Game.Database
|
|||||||
public interface IHasFiles<TFile>
|
public interface IHasFiles<TFile>
|
||||||
where TFile : INamedFileInfo
|
where TFile : INamedFileInfo
|
||||||
{
|
{
|
||||||
List<TFile> Files { get; set; }
|
[NotNull]
|
||||||
|
List<TFile> Files { get; }
|
||||||
|
|
||||||
string Hash { get; set; }
|
string Hash { get; set; }
|
||||||
}
|
}
|
||||||
|
15
osu.Game/Database/IHasNamedFiles.cs
Normal file
15
osu.Game/Database/IHasNamedFiles.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public interface IHasNamedFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All files used by this model.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<INamedFileUsage> Files { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,8 @@ namespace osu.Game.Database
|
|||||||
/// A wrapper to provide access to database backed classes in a thread-safe manner.
|
/// A wrapper to provide access to database backed classes in a thread-safe manner.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The databased type.</typeparam>
|
/// <typeparam name="T">The databased type.</typeparam>
|
||||||
public interface ILive<out T> where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more.
|
public interface ILive<T> : IEquatable<ILive<T>>
|
||||||
|
where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more.
|
||||||
{
|
{
|
||||||
Guid ID { get; }
|
Guid ID { get; }
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ namespace osu.Game.Database
|
|||||||
/// <param name="perform">The action to perform.</param>
|
/// <param name="perform">The action to perform.</param>
|
||||||
void PerformWrite(Action<T> perform);
|
void PerformWrite(Action<T> perform);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this instance is tracking data which is managed by the database backing.
|
||||||
|
/// </summary>
|
||||||
|
bool IsManaged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve the value of this instance on the current thread's context.
|
/// Resolve the value of this instance on the current thread's context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using osu.Game.IO;
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
@ -26,24 +23,6 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<TModel> ItemRemoved;
|
event Action<TModel> ItemRemoved;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
|
||||||
/// </summary>
|
|
||||||
Task ImportFromStableAsync(StableStorage stableStorage);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exports an item to a legacy (.zip based) package.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to export.</param>
|
|
||||||
void Export(TModel item);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exports an item to the given output stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="model">The item to export.</param>
|
|
||||||
/// <param name="outputStream">The output stream to export to.</param>
|
|
||||||
void ExportModelTo(TModel model, Stream outputStream);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an update of the specified item.
|
/// Perform an update of the specified item.
|
||||||
/// TODO: Support file additions/removals.
|
/// TODO: Support file additions/removals.
|
||||||
|
@ -8,7 +8,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
public interface IPostImports<out TModel>
|
public interface IPostImports<TModel>
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
18
osu.Game/Database/LegacyBeatmapExporter.cs
Normal file
18
osu.Game/Database/LegacyBeatmapExporter.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class LegacyBeatmapExporter : LegacyExporter<BeatmapSetInfo>
|
||||||
|
{
|
||||||
|
protected override string FileExtension => ".osz";
|
||||||
|
|
||||||
|
public LegacyBeatmapExporter(Storage storage)
|
||||||
|
: base(storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
osu.Game/Database/LegacyBeatmapImporter.cs
Normal file
21
osu.Game/Database/LegacyBeatmapImporter.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.IO;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class LegacyBeatmapImporter : LegacyModelImporter<BeatmapSetInfo>
|
||||||
|
{
|
||||||
|
protected override string ImportFromStablePath => ".";
|
||||||
|
|
||||||
|
protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage();
|
||||||
|
|
||||||
|
public LegacyBeatmapImporter(IModelImporter<BeatmapSetInfo> importer)
|
||||||
|
: base(importer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
osu.Game/Database/LegacyExporter.cs
Normal file
62
osu.Game/Database/LegacyExporter.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using SharpCompress.Archives.Zip;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class which handles exporting legacy user data of a single type from osu-stable.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class LegacyExporter<TModel>
|
||||||
|
where TModel : class, IHasNamedFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The file extension for exports (including the leading '.').
|
||||||
|
/// </summary>
|
||||||
|
protected abstract string FileExtension { get; }
|
||||||
|
|
||||||
|
protected readonly Storage UserFileStorage;
|
||||||
|
|
||||||
|
private readonly Storage exportStorage;
|
||||||
|
|
||||||
|
protected LegacyExporter(Storage storage)
|
||||||
|
{
|
||||||
|
exportStorage = storage.GetStorageForDirectory(@"exports");
|
||||||
|
UserFileStorage = storage.GetStorageForDirectory(@"files");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports an item to a legacy (.zip based) package.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to export.</param>
|
||||||
|
public void Export(TModel item)
|
||||||
|
{
|
||||||
|
string filename = $"{item.ToString().GetValidArchiveContentFilename()}{FileExtension}";
|
||||||
|
|
||||||
|
using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create))
|
||||||
|
ExportModelTo(item, stream);
|
||||||
|
|
||||||
|
exportStorage.PresentFileExternally(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports an item to the given output stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The item to export.</param>
|
||||||
|
/// <param name="outputStream">The output stream to export to.</param>
|
||||||
|
public virtual void ExportModelTo(TModel model, Stream outputStream)
|
||||||
|
{
|
||||||
|
using (var archive = ZipArchive.Create())
|
||||||
|
{
|
||||||
|
foreach (var file in model.Files)
|
||||||
|
archive.AddEntry(file.Filename, UserFileStorage.GetStream(file.File.GetStoragePath()));
|
||||||
|
|
||||||
|
archive.SaveTo(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,10 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
public class StableImportManager : Component
|
/// <summary>
|
||||||
|
/// Handles migration of legacy user data from osu-stable.
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyImportManager : Component
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SkinManager skins { get; set; }
|
private SkinManager skins { get; set; }
|
||||||
@ -53,16 +56,16 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
Task beatmapImportTask = Task.CompletedTask;
|
Task beatmapImportTask = Task.CompletedTask;
|
||||||
if (content.HasFlagFast(StableContent.Beatmaps))
|
if (content.HasFlagFast(StableContent.Beatmaps))
|
||||||
importTasks.Add(beatmapImportTask = beatmaps.ImportFromStableAsync(stableStorage));
|
importTasks.Add(beatmapImportTask = new LegacyBeatmapImporter(beatmaps).ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
if (content.HasFlagFast(StableContent.Skins))
|
if (content.HasFlagFast(StableContent.Skins))
|
||||||
importTasks.Add(skins.ImportFromStableAsync(stableStorage));
|
importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
if (content.HasFlagFast(StableContent.Collections))
|
if (content.HasFlagFast(StableContent.Collections))
|
||||||
importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
|
||||||
if (content.HasFlagFast(StableContent.Scores))
|
if (content.HasFlagFast(StableContent.Scores))
|
||||||
importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
|
||||||
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
||||||
}
|
}
|
60
osu.Game/Database/LegacyModelImporter.cs
Normal file
60
osu.Game/Database/LegacyModelImporter.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.IO;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class which handles importing legacy user data of a single type from osu-stable.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class LegacyModelImporter<TModel>
|
||||||
|
where TModel : class
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The relative path from osu-stable's data directory to import items from.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual string ImportFromStablePath => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in <see cref="ImportFromStablePath"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual IEnumerable<string> GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath)
|
||||||
|
.Select(path => storage.GetFullPath(path));
|
||||||
|
|
||||||
|
protected readonly IModelImporter<TModel> Importer;
|
||||||
|
|
||||||
|
protected LegacyModelImporter(IModelImporter<TModel> importer)
|
||||||
|
{
|
||||||
|
Importer = importer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||||
|
{
|
||||||
|
var storage = PrepareStableStorage(stableStorage);
|
||||||
|
|
||||||
|
// Handle situations like when the user does not have a Skins folder.
|
||||||
|
if (!storage.ExistsDirectory(ImportFromStablePath))
|
||||||
|
{
|
||||||
|
string fullPath = storage.GetFullPath(ImportFromStablePath);
|
||||||
|
|
||||||
|
Logger.Log(@$"Folder ""{fullPath}"" not available in the target osu!stable installation to import {Importer.HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.Run(async () => await Importer.Import(GetStableImportPaths(storage).ToArray()).ConfigureAwait(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run any required traversal operations on the stable storage location before performing operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stableStorage">The stable storage.</param>
|
||||||
|
/// <returns>The usable storage. Return the unchanged <paramref name="stableStorage"/> if no traversal is required.</returns>
|
||||||
|
protected virtual Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage;
|
||||||
|
}
|
||||||
|
}
|
31
osu.Game/Database/LegacyScoreExporter.cs
Normal file
31
osu.Game/Database/LegacyScoreExporter.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class LegacyScoreExporter : LegacyExporter<ScoreInfo>
|
||||||
|
{
|
||||||
|
protected override string FileExtension => ".osr";
|
||||||
|
|
||||||
|
public LegacyScoreExporter(Storage storage)
|
||||||
|
: base(storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExportModelTo(ScoreInfo model, Stream outputStream)
|
||||||
|
{
|
||||||
|
var file = model.Files.SingleOrDefault();
|
||||||
|
if (file == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var inputStream = UserFileStorage.GetStream(file.FileInfo.GetStoragePath()))
|
||||||
|
inputStream.CopyTo(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
osu.Game/Database/LegacyScoreImporter.cs
Normal file
26
osu.Game/Database/LegacyScoreImporter.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class LegacyScoreImporter : LegacyModelImporter<ScoreInfo>
|
||||||
|
{
|
||||||
|
protected override string ImportFromStablePath => Path.Combine("Data", "r");
|
||||||
|
|
||||||
|
protected override IEnumerable<string> GetStableImportPaths(Storage storage)
|
||||||
|
=> storage.GetFiles(ImportFromStablePath).Where(p => Importer.HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false))
|
||||||
|
.Select(path => storage.GetFullPath(path));
|
||||||
|
|
||||||
|
public LegacyScoreImporter(IModelImporter<ScoreInfo> importer)
|
||||||
|
: base(importer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
osu.Game/Database/LegacySkinExporter.cs
Normal file
18
osu.Game/Database/LegacySkinExporter.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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.Framework.Platform;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class LegacySkinExporter : LegacyExporter<SkinInfo>
|
||||||
|
{
|
||||||
|
protected override string FileExtension => ".osk";
|
||||||
|
|
||||||
|
public LegacySkinExporter(Storage storage)
|
||||||
|
: base(storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
osu.Game/Database/LegacySkinImporter.cs
Normal file
17
osu.Game/Database/LegacySkinImporter.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 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.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class LegacySkinImporter : LegacyModelImporter<SkinInfo>
|
||||||
|
{
|
||||||
|
protected override string ImportFromStablePath => "Skins";
|
||||||
|
|
||||||
|
public LegacySkinImporter(IModelImporter<SkinInfo> importer)
|
||||||
|
: base(importer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
@ -29,7 +28,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
protected readonly List<ArchiveDownloadRequest<T>> CurrentDownloads = new List<ArchiveDownloadRequest<T>>();
|
protected readonly List<ArchiveDownloadRequest<T>> CurrentDownloads = new List<ArchiveDownloadRequest<T>>();
|
||||||
|
|
||||||
protected ModelDownloader(IModelImporter<TModel> importer, IAPIProvider api, IIpcHost importHost = null)
|
protected ModelDownloader(IModelImporter<TModel> importer, IAPIProvider api)
|
||||||
{
|
{
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Statistics;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Stores;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
@ -121,6 +122,10 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clean up files after dropping any pending deletions.
|
||||||
|
// in the future we may want to only do this when the game is idle, rather than on every startup.
|
||||||
|
new RealmFileStore(this, storage).Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
public Guid ID { get; }
|
public Guid ID { get; }
|
||||||
|
|
||||||
|
public bool IsManaged { get; }
|
||||||
|
|
||||||
private readonly SynchronizationContext? fetchedContext;
|
private readonly SynchronizationContext? fetchedContext;
|
||||||
private readonly int fetchedThreadId;
|
private readonly int fetchedThreadId;
|
||||||
|
|
||||||
@ -33,8 +35,13 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
fetchedContext = SynchronizationContext.Current;
|
if (data.IsManaged)
|
||||||
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
|
{
|
||||||
|
IsManaged = true;
|
||||||
|
|
||||||
|
fetchedContext = SynchronizationContext.Current;
|
||||||
|
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
ID = data.ID;
|
ID = data.ID;
|
||||||
}
|
}
|
||||||
@ -75,13 +82,18 @@ namespace osu.Game.Database
|
|||||||
/// Perform a write operation on this live object.
|
/// Perform a write operation on this live object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="perform">The action to perform.</param>
|
/// <param name="perform">The action to perform.</param>
|
||||||
public void PerformWrite(Action<T> perform) =>
|
public void PerformWrite(Action<T> perform)
|
||||||
|
{
|
||||||
|
if (!IsManaged)
|
||||||
|
throw new InvalidOperationException("Can't perform writes on a non-managed underlying value");
|
||||||
|
|
||||||
PerformRead(t =>
|
PerformRead(t =>
|
||||||
{
|
{
|
||||||
var transaction = t.Realm.BeginWrite();
|
var transaction = t.Realm.BeginWrite();
|
||||||
perform(t);
|
perform(t);
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public T Value
|
public T Value
|
||||||
{
|
{
|
||||||
@ -102,10 +114,12 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool originalDataValid => isCorrectThread && data.IsValid;
|
private bool originalDataValid => !IsManaged || (isCorrectThread && data.IsValid);
|
||||||
|
|
||||||
// this matches realm's internal thread validation (see https://github.com/realm/realm-dotnet/blob/903b4d0b304f887e37e2d905384fb572a6496e70/Realm/Realm/Native/SynchronizationContextScheduler.cs#L72)
|
// this matches realm's internal thread validation (see https://github.com/realm/realm-dotnet/blob/903b4d0b304f887e37e2d905384fb572a6496e70/Realm/Realm/Native/SynchronizationContextScheduler.cs#L72)
|
||||||
private bool isCorrectThread
|
private bool isCorrectThread
|
||||||
=> (fetchedContext != null && SynchronizationContext.Current == fetchedContext) || fetchedThreadId == Thread.CurrentThread.ManagedThreadId;
|
=> (fetchedContext != null && SynchronizationContext.Current == fetchedContext) || fetchedThreadId == Thread.CurrentThread.ManagedThreadId;
|
||||||
|
|
||||||
|
public bool Equals(ILive<T>? other) => ID == other?.ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
@ -124,5 +125,21 @@ namespace osu.Game.Extensions
|
|||||||
|
|
||||||
return instance.OnlineID.Equals(other.OnlineID);
|
return instance.OnlineID.Equals(other.OnlineID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly char[] invalid_filename_characters = Path.GetInvalidFileNameChars()
|
||||||
|
// Backslash is added to avoid issues when exporting to zip.
|
||||||
|
// See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143.
|
||||||
|
.Append('\\')
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a valid filename for use inside a zip file. Avoids backslashes being incorrectly converted to directories.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetValidArchiveContentFilename(this string filename)
|
||||||
|
{
|
||||||
|
foreach (char c in invalid_filename_characters)
|
||||||
|
filename = filename.Replace(c, '_');
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
// 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;
|
||||||
@ -31,13 +28,6 @@ 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())
|
||||||
|
@ -76,7 +76,6 @@ namespace osu.Game.Models
|
|||||||
public bool Equals(IBeatmapSetInfo? other) => other is RealmBeatmapSet b && Equals(b);
|
public bool Equals(IBeatmapSetInfo? other) => other is RealmBeatmapSet b && Equals(b);
|
||||||
|
|
||||||
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
|
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
|
||||||
|
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => Files;
|
||||||
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => metadata;
|
IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => metadata;
|
||||||
|
|
||||||
DateTimeOffset IBeatmapSetInfo.DateAdded => throw new NotImplementedException();
|
DateTimeOffset IBeatmapSetInfo.DateAdded => throw new NotImplementedException();
|
||||||
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => throw new NotImplementedException();
|
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException();
|
||||||
double IBeatmapSetInfo.MaxStarDifficulty => throw new NotImplementedException();
|
double IBeatmapSetInfo.MaxStarDifficulty => throw new NotImplementedException();
|
||||||
double IBeatmapSetInfo.MaxLength => throw new NotImplementedException();
|
double IBeatmapSetInfo.MaxLength => throw new NotImplementedException();
|
||||||
double IBeatmapSetInfo.MaxBPM => BPM;
|
double IBeatmapSetInfo.MaxBPM => BPM;
|
||||||
|
@ -8,6 +8,7 @@ using JetBrains.Annotations;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -147,6 +148,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
|
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
|
||||||
|
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException();
|
||||||
|
|
||||||
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
|
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@ namespace osu.Game.Online
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
protected BeatmapManager? Manager { get; private set; }
|
protected BeatmapManager? Manager { get; private set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
protected BeatmapModelDownloader? Downloader { get; private set; }
|
||||||
|
|
||||||
private ArchiveDownloadRequest<IBeatmapSetInfo>? attachedRequest;
|
private ArchiveDownloadRequest<IBeatmapSetInfo>? attachedRequest;
|
||||||
|
|
||||||
public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem)
|
public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem)
|
||||||
@ -25,7 +28,7 @@ namespace osu.Game.Online
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (Manager == null)
|
if (Manager == null || Downloader == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||||
@ -34,10 +37,10 @@ namespace osu.Game.Online
|
|||||||
if (Manager.IsAvailableLocally(beatmapSetInfo))
|
if (Manager.IsAvailableLocally(beatmapSetInfo))
|
||||||
UpdateState(DownloadState.LocallyAvailable);
|
UpdateState(DownloadState.LocallyAvailable);
|
||||||
else
|
else
|
||||||
attachDownload(Manager.GetExistingDownload(beatmapSetInfo));
|
attachDownload(Downloader.GetExistingDownload(beatmapSetInfo));
|
||||||
|
|
||||||
Manager.DownloadBegan += downloadBegan;
|
Downloader.DownloadBegan += downloadBegan;
|
||||||
Manager.DownloadFailed += downloadFailed;
|
Downloader.DownloadFailed += downloadFailed;
|
||||||
Manager.ItemUpdated += itemUpdated;
|
Manager.ItemUpdated += itemUpdated;
|
||||||
Manager.ItemRemoved += itemRemoved;
|
Manager.ItemRemoved += itemRemoved;
|
||||||
}
|
}
|
||||||
@ -115,10 +118,14 @@ namespace osu.Game.Online
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
attachDownload(null);
|
attachDownload(null);
|
||||||
|
|
||||||
|
if (Downloader != null)
|
||||||
|
{
|
||||||
|
Downloader.DownloadBegan -= downloadBegan;
|
||||||
|
Downloader.DownloadFailed -= downloadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
if (Manager != null)
|
if (Manager != null)
|
||||||
{
|
{
|
||||||
Manager.DownloadBegan -= downloadBegan;
|
|
||||||
Manager.DownloadFailed -= downloadFailed;
|
|
||||||
Manager.ItemUpdated -= itemUpdated;
|
Manager.ItemUpdated -= itemUpdated;
|
||||||
Manager.ItemRemoved -= itemRemoved;
|
Manager.ItemRemoved -= itemRemoved;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
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;
|
||||||
@ -66,6 +68,9 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreManager scoreManager { get; set; }
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Storage storage { get; set; }
|
||||||
|
|
||||||
public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true)
|
public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true)
|
||||||
{
|
{
|
||||||
Score = score;
|
Score = score;
|
||||||
@ -394,8 +399,8 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
||||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||||
|
|
||||||
if (Score.Files?.Count > 0)
|
if (Score.Files.Count > 0)
|
||||||
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score)));
|
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score)));
|
||||||
|
|
||||||
if (Score.ID != 0)
|
if (Score.ID != 0)
|
||||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
||||||
|
@ -15,6 +15,9 @@ namespace osu.Game.Online
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
protected ScoreManager? Manager { get; private set; }
|
protected ScoreManager? Manager { get; private set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
protected ScoreModelDownloader? Downloader { get; private set; }
|
||||||
|
|
||||||
private ArchiveDownloadRequest<IScoreInfo>? attachedRequest;
|
private ArchiveDownloadRequest<IScoreInfo>? attachedRequest;
|
||||||
|
|
||||||
public ScoreDownloadTracker(ScoreInfo trackedItem)
|
public ScoreDownloadTracker(ScoreInfo trackedItem)
|
||||||
@ -25,7 +28,7 @@ namespace osu.Game.Online
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (Manager == null)
|
if (Manager == null || Downloader == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||||
@ -38,10 +41,10 @@ namespace osu.Game.Online
|
|||||||
if (Manager.IsAvailableLocally(scoreInfo))
|
if (Manager.IsAvailableLocally(scoreInfo))
|
||||||
UpdateState(DownloadState.LocallyAvailable);
|
UpdateState(DownloadState.LocallyAvailable);
|
||||||
else
|
else
|
||||||
attachDownload(Manager.GetExistingDownload(scoreInfo));
|
attachDownload(Downloader.GetExistingDownload(scoreInfo));
|
||||||
|
|
||||||
Manager.DownloadBegan += downloadBegan;
|
Downloader.DownloadBegan += downloadBegan;
|
||||||
Manager.DownloadFailed += downloadFailed;
|
Downloader.DownloadFailed += downloadFailed;
|
||||||
Manager.ItemUpdated += itemUpdated;
|
Manager.ItemUpdated += itemUpdated;
|
||||||
Manager.ItemRemoved += itemRemoved;
|
Manager.ItemRemoved += itemRemoved;
|
||||||
}
|
}
|
||||||
@ -119,10 +122,14 @@ namespace osu.Game.Online
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
attachDownload(null);
|
attachDownload(null);
|
||||||
|
|
||||||
|
if (Downloader != null)
|
||||||
|
{
|
||||||
|
Downloader.DownloadBegan -= downloadBegan;
|
||||||
|
Downloader.DownloadFailed -= downloadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
if (Manager != null)
|
if (Manager != null)
|
||||||
{
|
{
|
||||||
Manager.DownloadBegan -= downloadBegan;
|
|
||||||
Manager.DownloadFailed -= downloadFailed;
|
|
||||||
Manager.ItemUpdated -= itemUpdated;
|
Manager.ItemUpdated -= itemUpdated;
|
||||||
Manager.ItemRemoved -= itemRemoved;
|
Manager.ItemRemoved -= itemRemoved;
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game
|
|||||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly StableImportManager stableImportManager = new StableImportManager();
|
private readonly LegacyImportManager legacyImportManager = new LegacyImportManager();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
||||||
@ -656,6 +656,9 @@ namespace osu.Game
|
|||||||
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
||||||
BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value);
|
BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value);
|
||||||
|
|
||||||
|
BeatmapDownloader.PostNotification = n => Notifications.Post(n);
|
||||||
|
ScoreDownloader.PostNotification = n => Notifications.Post(n);
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => Notifications.Post(n);
|
ScoreManager.PostNotification = n => Notifications.Post(n);
|
||||||
ScoreManager.PostImport = items => PresentScore(items.First().Value);
|
ScoreManager.PostImport = items => PresentScore(items.First().Value);
|
||||||
|
|
||||||
@ -782,7 +785,7 @@ namespace osu.Game
|
|||||||
PostNotification = n => Notifications.Post(n),
|
PostNotification = n => Notifications.Post(n),
|
||||||
}, Add, true);
|
}, Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(stableImportManager, Add);
|
loadComponentSingleFile(legacyImportManager, Add);
|
||||||
|
|
||||||
loadComponentSingleFile(screenshotManager, Add);
|
loadComponentSingleFile(screenshotManager, Add);
|
||||||
|
|
||||||
|
@ -96,8 +96,12 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected BeatmapManager BeatmapManager { get; private set; }
|
protected BeatmapManager BeatmapManager { get; private set; }
|
||||||
|
|
||||||
|
protected BeatmapModelDownloader BeatmapDownloader { get; private set; }
|
||||||
|
|
||||||
protected ScoreManager ScoreManager { get; private set; }
|
protected ScoreManager ScoreManager { get; private set; }
|
||||||
|
|
||||||
|
protected ScoreModelDownloader ScoreDownloader { get; private set; }
|
||||||
|
|
||||||
protected SkinManager SkinManager { get; private set; }
|
protected SkinManager SkinManager { get; private set; }
|
||||||
|
|
||||||
protected RulesetStore RulesetStore { get; private set; }
|
protected RulesetStore RulesetStore { get; private set; }
|
||||||
@ -232,9 +236,12 @@ namespace osu.Game
|
|||||||
dependencies.Cache(fileStore = new FileStore(contextFactory, Storage));
|
dependencies.Cache(fileStore = new FileStore(contextFactory, Storage));
|
||||||
|
|
||||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
|
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
|
||||||
|
|
||||||
|
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
|
||||||
|
dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API));
|
||||||
|
|
||||||
// the following realm components are not actively used yet, but initialised and kept up to date for initial testing.
|
// the following realm components are not actively used yet, but initialised and kept up to date for initial testing.
|
||||||
realmRulesetStore = new RealmRulesetStore(realmFactory, Storage);
|
realmRulesetStore = new RealmRulesetStore(realmFactory, Storage);
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
sortControlBackground.Colour = colourProvider.Background5;
|
sortControlBackground.Colour = colourProvider.Background4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Search(string query)
|
public void Search(string query)
|
||||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig)
|
private void load(OsuGame game, BeatmapModelDownloader beatmaps, OsuConfigManager osuConfig)
|
||||||
{
|
{
|
||||||
noVideoSetting = osuConfig.GetBindable<bool>(OsuSetting.PreferNoVideo);
|
noVideoSetting = osuConfig.GetBindable<bool>(OsuSetting.PreferNoVideo);
|
||||||
|
|
||||||
|
@ -15,12 +15,11 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -34,7 +33,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private Drawable currentContent;
|
private Drawable currentContent;
|
||||||
private Container panelTarget;
|
private Container panelTarget;
|
||||||
private FillFlowContainer<BeatmapPanel> foundContent;
|
private FillFlowContainer<BeatmapCard> foundContent;
|
||||||
private NotFoundDrawable notFoundContent;
|
private NotFoundDrawable notFoundContent;
|
||||||
private SupporterRequiredDrawable supporterRequiredContent;
|
private SupporterRequiredDrawable supporterRequiredContent;
|
||||||
private BeatmapListingFilterControl filterControl;
|
private BeatmapListingFilterControl filterControl;
|
||||||
@ -69,7 +68,7 @@ namespace osu.Game.Overlays
|
|||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = ColourProvider.Background4,
|
Colour = ColourProvider.Background5,
|
||||||
},
|
},
|
||||||
panelTarget = new Container
|
panelTarget = new Container
|
||||||
{
|
{
|
||||||
@ -79,7 +78,7 @@ namespace osu.Game.Overlays
|
|||||||
Padding = new MarginPadding { Horizontal = 20 },
|
Padding = new MarginPadding { Horizontal = 20 },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
foundContent = new FillFlowContainer<BeatmapPanel>(),
|
foundContent = new FillFlowContainer<BeatmapCard>(),
|
||||||
notFoundContent = new NotFoundDrawable(),
|
notFoundContent = new NotFoundDrawable(),
|
||||||
supporterRequiredContent = new SupporterRequiredDrawable(),
|
supporterRequiredContent = new SupporterRequiredDrawable(),
|
||||||
}
|
}
|
||||||
@ -136,7 +135,7 @@ namespace osu.Game.Overlays
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newPanels = searchResult.Results.Select<APIBeatmapSet, BeatmapPanel>(b => new GridBeatmapPanel(b)
|
var newPanels = searchResult.Results.Select(b => new BeatmapCard(b)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
@ -152,7 +151,7 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spawn new children with the contained so we only clear old content at the last moment.
|
// spawn new children with the contained so we only clear old content at the last moment.
|
||||||
var content = new FillFlowContainer<BeatmapPanel>
|
var content = new FillFlowContainer<BeatmapCard>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IAPIProvider api, BeatmapManager beatmaps)
|
private void load(IAPIProvider api, BeatmapModelDownloader beatmaps)
|
||||||
{
|
{
|
||||||
FillFlowContainer textSprites;
|
FillFlowContainer textSprites;
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
|
|||||||
new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
|
new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
|
||||||
|
|
||||||
protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0
|
protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0
|
||||||
? new GridBeatmapPanel(model)
|
? new BeatmapCard(model)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
|
@ -13,9 +13,9 @@ using osu.Game.Online.API.Requests;
|
|||||||
using osu.Game.Overlays.Rankings.Tables;
|
using osu.Game.Overlays.Rankings.Tables;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Rankings
|
namespace osu.Game.Overlays.Rankings
|
||||||
{
|
{
|
||||||
@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Rankings
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Spacing = new Vector2(10),
|
Spacing = new Vector2(10),
|
||||||
Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b)
|
Children = response.BeatmapSets.Select(b => new BeatmapCard(b)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
|
@ -31,9 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
private SettingsButton undeleteButton;
|
private SettingsButton undeleteButton;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, DialogOverlay dialogOverlay)
|
||||||
{
|
{
|
||||||
if (stableImportManager?.SupportsImportFromStable == true)
|
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importBeatmapsButton = new SettingsButton
|
Add(importBeatmapsButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importBeatmapsButton.Enabled.Value = false;
|
importBeatmapsButton.Enabled.Value = false;
|
||||||
stableImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true));
|
legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stableImportManager?.SupportsImportFromStable == true)
|
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importScoresButton = new SettingsButton
|
Add(importScoresButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importScoresButton.Enabled.Value = false;
|
importScoresButton.Enabled.Value = false;
|
||||||
stableImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true));
|
legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stableImportManager?.SupportsImportFromStable == true)
|
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importSkinsButton = new SettingsButton
|
Add(importSkinsButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importSkinsButton.Enabled.Value = false;
|
importSkinsButton.Enabled.Value = false;
|
||||||
stableImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true));
|
legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
|
|
||||||
if (collectionManager != null)
|
if (collectionManager != null)
|
||||||
{
|
{
|
||||||
if (stableImportManager?.SupportsImportFromStable == true)
|
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importCollectionsButton = new SettingsButton
|
Add(importCollectionsButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importCollectionsButton.Enabled.Value = false;
|
importCollectionsButton.Enabled.Value = false;
|
||||||
stableImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true));
|
legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -167,6 +169,9 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private SkinManager skins { get; set; }
|
private SkinManager skins { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Storage storage { get; set; }
|
||||||
|
|
||||||
private Bindable<Skin> currentSkin;
|
private Bindable<Skin> currentSkin;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -183,7 +188,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
skins.Export(currentSkin.Value.SkinInfo);
|
new LegacySkinExporter(storage).Export(currentSkin.Value.SkinInfo);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
public interface IScoreInfo : IHasOnlineID<long>
|
public interface IScoreInfo : IHasOnlineID<long>, IHasNamedFiles
|
||||||
{
|
{
|
||||||
APIUser User { get; }
|
APIUser User { get; }
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ using osu.Game.IO;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey
|
public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage
|
||||||
{
|
{
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
|
|
||||||
@ -17,5 +17,7 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
|
|
||||||
|
IFileInfo INamedFileUsage.File => FileInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ namespace osu.Game.Scoring
|
|||||||
[NotMapped]
|
[NotMapped]
|
||||||
public List<HitEvent> HitEvents { get; set; }
|
public List<HitEvent> HitEvents { get; set; }
|
||||||
|
|
||||||
public List<ScoreFileInfo> Files { get; set; }
|
public List<ScoreFileInfo> Files { get; } = new List<ScoreFileInfo>();
|
||||||
|
|
||||||
public string Hash { get; set; }
|
public string Hash { get; set; }
|
||||||
|
|
||||||
@ -257,5 +257,7 @@ namespace osu.Game.Scoring
|
|||||||
bool IScoreInfo.HasReplay => Files.Any();
|
bool IScoreInfo.HasReplay => Files.Any();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => Files;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -15,9 +14,7 @@ using osu.Framework.Threading;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -25,15 +22,14 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
public class ScoreManager : IModelManager<ScoreInfo>, IModelImporter<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<IScoreInfo>
|
public class ScoreManager : IModelManager<ScoreInfo>, IModelImporter<ScoreInfo>
|
||||||
{
|
{
|
||||||
private readonly Scheduler scheduler;
|
private readonly Scheduler scheduler;
|
||||||
private readonly Func<BeatmapDifficultyCache> difficulties;
|
private readonly Func<BeatmapDifficultyCache> difficulties;
|
||||||
private readonly OsuConfigManager configManager;
|
private readonly OsuConfigManager configManager;
|
||||||
private readonly ScoreModelManager scoreModelManager;
|
private readonly ScoreModelManager scoreModelManager;
|
||||||
private readonly ScoreModelDownloader scoreModelDownloader;
|
|
||||||
|
|
||||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, Scheduler scheduler,
|
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IDatabaseContextFactory contextFactory, Scheduler scheduler,
|
||||||
IIpcHost importHost = null, Func<BeatmapDifficultyCache> difficulties = null, OsuConfigManager configManager = null)
|
IIpcHost importHost = null, Func<BeatmapDifficultyCache> difficulties = null, OsuConfigManager configManager = null)
|
||||||
{
|
{
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
@ -41,7 +37,6 @@ namespace osu.Game.Scoring
|
|||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
|
|
||||||
scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory, importHost);
|
scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory, importHost);
|
||||||
scoreModelDownloader = new ScoreModelDownloader(scoreModelManager, api, importHost);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score);
|
public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score);
|
||||||
@ -240,11 +235,7 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
public Action<Notification> PostNotification
|
public Action<Notification> PostNotification
|
||||||
{
|
{
|
||||||
set
|
set => scoreModelManager.PostNotification = value;
|
||||||
{
|
|
||||||
scoreModelManager.PostNotification = value;
|
|
||||||
scoreModelDownloader.PostNotification = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -263,21 +254,6 @@ namespace osu.Game.Scoring
|
|||||||
remove => scoreModelManager.ItemRemoved -= value;
|
remove => scoreModelManager.ItemRemoved -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ImportFromStableAsync(StableStorage stableStorage)
|
|
||||||
{
|
|
||||||
return scoreModelManager.ImportFromStableAsync(stableStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Export(ScoreInfo item)
|
|
||||||
{
|
|
||||||
scoreModelManager.Export(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExportModelTo(ScoreInfo model, Stream outputStream)
|
|
||||||
{
|
|
||||||
scoreModelManager.ExportModelTo(model, outputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ScoreInfo item)
|
public void Update(ScoreInfo item)
|
||||||
{
|
{
|
||||||
scoreModelManager.Update(item);
|
scoreModelManager.Update(item);
|
||||||
@ -342,49 +318,6 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Implementation of IModelFileManager<in ScoreInfo,in ScoreFileInfo>
|
|
||||||
|
|
||||||
public void ReplaceFile(ScoreInfo model, ScoreFileInfo file, Stream contents, string filename = null)
|
|
||||||
{
|
|
||||||
scoreModelManager.ReplaceFile(model, file, contents, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteFile(ScoreInfo model, ScoreFileInfo file)
|
|
||||||
{
|
|
||||||
scoreModelManager.DeleteFile(model, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddFile(ScoreInfo model, Stream contents, string filename)
|
|
||||||
{
|
|
||||||
scoreModelManager.AddFile(model, contents, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementation of IModelDownloader<IScoreInfo>
|
|
||||||
|
|
||||||
public event Action<ArchiveDownloadRequest<IScoreInfo>> DownloadBegan
|
|
||||||
{
|
|
||||||
add => scoreModelDownloader.DownloadBegan += value;
|
|
||||||
remove => scoreModelDownloader.DownloadBegan -= value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<ArchiveDownloadRequest<IScoreInfo>> DownloadFailed
|
|
||||||
{
|
|
||||||
add => scoreModelDownloader.DownloadFailed += value;
|
|
||||||
remove => scoreModelDownloader.DownloadFailed -= value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Download(IScoreInfo model, bool minimiseDownloadSize) =>
|
|
||||||
scoreModelDownloader.Download(model, minimiseDownloadSize);
|
|
||||||
|
|
||||||
public ArchiveDownloadRequest<IScoreInfo> GetExistingDownload(IScoreInfo model)
|
|
||||||
{
|
|
||||||
return scoreModelDownloader.GetExistingDownload(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementation of IPresentImports<ScoreInfo>
|
#region Implementation of IPresentImports<ScoreInfo>
|
||||||
|
|
||||||
public Action<IEnumerable<ILive<ScoreInfo>>> PostImport
|
public Action<IEnumerable<ILive<ScoreInfo>>> PostImport
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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.Platform;
|
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
@ -10,8 +9,8 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
public class ScoreModelDownloader : ModelDownloader<ScoreInfo, IScoreInfo>
|
public class ScoreModelDownloader : ModelDownloader<ScoreInfo, IScoreInfo>
|
||||||
{
|
{
|
||||||
public ScoreModelDownloader(IModelImporter<ScoreInfo> scoreManager, IAPIProvider api, IIpcHost importHost = null)
|
public ScoreModelDownloader(IModelImporter<ScoreInfo> scoreManager, IAPIProvider api)
|
||||||
: base(scoreManager, api, importHost)
|
: base(scoreManager, api)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -13,7 +12,6 @@ using osu.Framework.Logging;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
@ -26,8 +24,6 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osr" };
|
protected override string[] HashableFileTypes => new[] { ".osr" };
|
||||||
|
|
||||||
protected override string ImportFromStablePath => Path.Combine("Data", "r");
|
|
||||||
|
|
||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly Func<BeatmapManager> beatmaps;
|
private readonly Func<BeatmapManager> beatmaps;
|
||||||
|
|
||||||
@ -71,19 +67,5 @@ namespace osu.Game.Scoring
|
|||||||
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
|
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
|
||||||
=> base.CheckLocalAvailability(model, items)
|
=> base.CheckLocalAvailability(model, items)
|
||||||
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID));
|
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID));
|
||||||
|
|
||||||
public override void ExportModelTo(ScoreInfo model, Stream outputStream)
|
|
||||||
{
|
|
||||||
var file = model.Files.SingleOrDefault();
|
|
||||||
if (file == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using (var inputStream = Files.Storage.GetStream(file.FileInfo.GetStoragePath()))
|
|
||||||
inputStream.CopyTo(outputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<string> GetStableImportPaths(Storage storage)
|
|
||||||
=> storage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false))
|
|
||||||
.Select(path => storage.GetFullPath(path));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,11 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -63,6 +65,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
private BeatmapManager beatmapManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Storage storage { get; set; }
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private DialogOverlay dialogOverlay { get; set; }
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
@ -753,7 +758,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
private void exportBeatmap()
|
private void exportBeatmap()
|
||||||
{
|
{
|
||||||
Save();
|
Save();
|
||||||
beatmapManager.Export(Beatmap.Value.BeatmapSetInfo);
|
new LegacyBeatmapExporter(storage).Export(Beatmap.Value.BeatmapSetInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLastSavedHash()
|
private void updateLastSavedHash()
|
||||||
|
@ -18,11 +18,15 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
private readonly bool allowEdit;
|
private readonly bool allowEdit;
|
||||||
private readonly bool allowSelection;
|
private readonly bool allowSelection;
|
||||||
|
private readonly bool showItemOwner;
|
||||||
|
|
||||||
public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool reverse = false)
|
public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool reverse = false, bool showItemOwner = false)
|
||||||
{
|
{
|
||||||
this.allowEdit = allowEdit;
|
this.allowEdit = allowEdit;
|
||||||
this.allowSelection = allowSelection;
|
this.allowSelection = allowSelection;
|
||||||
|
this.showItemOwner = showItemOwner;
|
||||||
|
|
||||||
|
((ReversibleFillFlowContainer)ListContainer).Reverse = reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -52,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
Spacing = new Vector2(0, 2)
|
Spacing = new Vector2(0, 2)
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override OsuRearrangeableListItem<PlaylistItem> CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection)
|
protected override OsuRearrangeableListItem<PlaylistItem> CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection, showItemOwner)
|
||||||
{
|
{
|
||||||
SelectedItem = { BindTarget = SelectedItem },
|
SelectedItem = { BindTarget = SelectedItem },
|
||||||
RequestDeletion = requestDeletion
|
RequestDeletion = requestDeletion
|
||||||
|
@ -4,17 +4,21 @@
|
|||||||
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.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -26,6 +30,7 @@ using osu.Game.Overlays.BeatmapSet;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -34,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>
|
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 50;
|
public const float HEIGHT = 50;
|
||||||
|
public const float ICON_HEIGHT = 34;
|
||||||
|
|
||||||
public Action<PlaylistItem> RequestDeletion;
|
public Action<PlaylistItem> RequestDeletion;
|
||||||
|
|
||||||
@ -45,6 +51,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
private LinkFlowContainer authorText;
|
private LinkFlowContainer authorText;
|
||||||
private ExplicitContentBeatmapPill explicitContentPill;
|
private ExplicitContentBeatmapPill explicitContentPill;
|
||||||
private ModDisplay modDisplay;
|
private ModDisplay modDisplay;
|
||||||
|
private UpdateableAvatar ownerAvatar;
|
||||||
|
|
||||||
private readonly IBindable<bool> valid = new Bindable<bool>();
|
private readonly IBindable<bool> valid = new Bindable<bool>();
|
||||||
|
|
||||||
@ -54,12 +61,19 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
public readonly PlaylistItem Item;
|
public readonly PlaylistItem Item;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private UserLookupCache userLookupCache { get; set; }
|
||||||
|
|
||||||
private readonly bool allowEdit;
|
private readonly bool allowEdit;
|
||||||
private readonly bool allowSelection;
|
private readonly bool allowSelection;
|
||||||
|
private readonly bool showItemOwner;
|
||||||
|
|
||||||
protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model;
|
protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model;
|
||||||
|
|
||||||
public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection)
|
public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner)
|
||||||
: base(item)
|
: base(item)
|
||||||
{
|
{
|
||||||
Item = item;
|
Item = item;
|
||||||
@ -67,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
// TODO: edit support should be moved out into a derived class
|
// TODO: edit support should be moved out into a derived class
|
||||||
this.allowEdit = allowEdit;
|
this.allowEdit = allowEdit;
|
||||||
this.allowSelection = allowSelection;
|
this.allowSelection = allowSelection;
|
||||||
|
this.showItemOwner = showItemOwner;
|
||||||
|
|
||||||
beatmap.BindTo(item.Beatmap);
|
beatmap.BindTo(item.Beatmap);
|
||||||
valid.BindTo(item.Valid);
|
valid.BindTo(item.Valid);
|
||||||
@ -79,9 +94,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
Colour = OsuColour.Gray(0.5f);
|
Colour = OsuColour.Gray(0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; }
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -132,7 +144,13 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
maskingContainer.BorderColour = colours.Red;
|
maskingContainer.BorderColour = colours.Red;
|
||||||
}
|
}
|
||||||
|
|
||||||
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) };
|
if (showItemOwner)
|
||||||
|
{
|
||||||
|
ownerAvatar.Show();
|
||||||
|
userLookupCache.GetUserAsync(Item.OwnerID).ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
|
}
|
||||||
|
|
||||||
|
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) };
|
||||||
|
|
||||||
panelBackground.Beatmap.Value = Item.Beatmap.Value;
|
panelBackground.Beatmap.Value = Item.Beatmap.Value;
|
||||||
|
|
||||||
@ -186,6 +204,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
},
|
},
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
@ -196,7 +215,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Margin = new MarginPadding { Left = 8, Right = 8, },
|
Margin = new MarginPadding { Left = 8, Right = 8 },
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -259,7 +278,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Margin = new MarginPadding { Left = 8, Right = 10, },
|
Margin = new MarginPadding { Horizontal = 8 },
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Spacing = new Vector2(5),
|
Spacing = new Vector2(5),
|
||||||
ChildrenEnumerable = CreateButtons().Select(button => button.With(b =>
|
ChildrenEnumerable = CreateButtons().Select(button => button.With(b =>
|
||||||
@ -267,7 +286,17 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
b.Anchor = Anchor.Centre;
|
b.Anchor = Anchor.Centre;
|
||||||
b.Origin = Anchor.Centre;
|
b.Origin = Anchor.Centre;
|
||||||
}))
|
}))
|
||||||
}
|
},
|
||||||
|
ownerAvatar = new OwnerAvatar
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(ICON_HEIGHT),
|
||||||
|
Margin = new MarginPadding { Right = 8 },
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 4,
|
||||||
|
Alpha = showItemOwner ? 1 : 0
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -417,5 +446,31 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
Beatmap.BindValueChanged(beatmap => backgroundSprite.Beatmap.Value = beatmap.NewValue);
|
Beatmap.BindValueChanged(beatmap => backgroundSprite.Beatmap.Value = beatmap.NewValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class OwnerAvatar : UpdateableAvatar, IHasTooltip
|
||||||
|
{
|
||||||
|
public OwnerAvatar()
|
||||||
|
{
|
||||||
|
AddInternal(new TooltipArea(this)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Depth = -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => User == null ? "loading user..." : $"queued by {User.Username}";
|
||||||
|
|
||||||
|
private class TooltipArea : Component, IHasTooltip
|
||||||
|
{
|
||||||
|
private readonly OwnerAvatar avatar;
|
||||||
|
|
||||||
|
public TooltipArea(OwnerAvatar avatar)
|
||||||
|
{
|
||||||
|
this.avatar = avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => avatar.TooltipText;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,16 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
{
|
{
|
||||||
public Action<PlaylistItem> RequestShowResults;
|
public Action<PlaylistItem> RequestShowResults;
|
||||||
|
|
||||||
public DrawableRoomPlaylistWithResults()
|
private readonly bool showItemOwner;
|
||||||
: base(false, true)
|
|
||||||
|
public DrawableRoomPlaylistWithResults(bool showItemOwner = false)
|
||||||
|
: base(false, true, showItemOwner: showItemOwner)
|
||||||
{
|
{
|
||||||
|
this.showItemOwner = showItemOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override OsuRearrangeableListItem<PlaylistItem> CreateOsuDrawable(PlaylistItem item) =>
|
protected override OsuRearrangeableListItem<PlaylistItem> CreateOsuDrawable(PlaylistItem item) =>
|
||||||
new DrawableRoomPlaylistItemWithResults(item, false, true)
|
new DrawableRoomPlaylistItemWithResults(item, false, true, showItemOwner)
|
||||||
{
|
{
|
||||||
RequestShowResults = () => RequestShowResults(item),
|
RequestShowResults = () => RequestShowResults(item),
|
||||||
SelectedItem = { BindTarget = SelectedItem },
|
SelectedItem = { BindTarget = SelectedItem },
|
||||||
@ -35,8 +38,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
{
|
{
|
||||||
public Action RequestShowResults;
|
public Action RequestShowResults;
|
||||||
|
|
||||||
public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection)
|
public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner)
|
||||||
: base(item, allowEdit, allowSelection)
|
: base(item, allowEdit, allowSelection, showItemOwner)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +302,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
public override void OnResuming(IScreen last)
|
public override void OnResuming(IScreen last)
|
||||||
{
|
{
|
||||||
base.OnResuming(last);
|
base.OnResuming(last);
|
||||||
|
updateWorkingBeatmap();
|
||||||
beginHandlingTrack();
|
beginHandlingTrack();
|
||||||
Scheduler.AddOnce(UpdateMods);
|
Scheduler.AddOnce(UpdateMods);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
null,
|
null,
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
playlist = new DrawableRoomPlaylist(false, false, true)
|
playlist = new DrawableRoomPlaylist(false, false, true, true)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -20,7 +21,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
@ -49,6 +50,12 @@ namespace osu.Game.Screens.Play
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapModelDownloader beatmapDownloader { get; set; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
private Container beatmapPanelContainer;
|
private Container beatmapPanelContainer;
|
||||||
private TriangleButton watchButton;
|
private TriangleButton watchButton;
|
||||||
private SettingsCheckbox automaticDownload;
|
private SettingsCheckbox automaticDownload;
|
||||||
@ -70,7 +77,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
InternalChild = new Container
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
@ -85,7 +92,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Colour = colours.GreySeafoamDark,
|
Colour = colourProvider.Background5,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
@ -226,7 +233,7 @@ namespace osu.Game.Screens.Play
|
|||||||
onlineBeatmapRequest.Success += beatmapSet => Schedule(() =>
|
onlineBeatmapRequest.Success += beatmapSet => Schedule(() =>
|
||||||
{
|
{
|
||||||
this.beatmapSet = beatmapSet;
|
this.beatmapSet = beatmapSet;
|
||||||
beatmapPanelContainer.Child = new GridBeatmapPanel(this.beatmapSet);
|
beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet);
|
||||||
checkForAutomaticDownload();
|
checkForAutomaticDownload();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -244,7 +251,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID }))
|
if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID }))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
beatmaps.Download(beatmapSet);
|
beatmapDownloader.Download(beatmapSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuGame game, ScoreManager scores)
|
private void load(OsuGame game, ScoreModelDownloader scores)
|
||||||
{
|
{
|
||||||
InternalChild = shakeContainer = new ShakeContainer
|
InternalChild = shakeContainer = new ShakeContainer
|
||||||
{
|
{
|
||||||
@ -65,7 +65,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case DownloadState.NotDownloaded:
|
case DownloadState.NotDownloaded:
|
||||||
scores.Download(Score.Value, false);
|
scores.Download(Score.Value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DownloadState.Importing:
|
case DownloadState.Importing:
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected virtual bool ShowFooter => true;
|
protected virtual bool ShowFooter => true;
|
||||||
|
|
||||||
protected virtual bool DisplayStableImportPrompt => stableImportManager?.SupportsImportFromStable == true;
|
protected virtual bool DisplayStableImportPrompt => legacyImportManager?.SupportsImportFromStable == true;
|
||||||
|
|
||||||
public override bool? AllowTrackAdjustments => true;
|
public override bool? AllowTrackAdjustments => true;
|
||||||
|
|
||||||
@ -76,6 +76,8 @@ namespace osu.Game.Screens.Select
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool AllowEditing => true;
|
public virtual bool AllowEditing => true;
|
||||||
|
|
||||||
|
public bool BeatmapSetsLoaded => IsLoaded && Carousel?.BeatmapSetsLoaded == true;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
||||||
|
|
||||||
@ -90,7 +92,7 @@ namespace osu.Game.Screens.Select
|
|||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private StableImportManager stableImportManager { get; set; }
|
private LegacyImportManager legacyImportManager { get; set; }
|
||||||
|
|
||||||
protected ModSelectOverlay ModSelect { get; private set; }
|
protected ModSelectOverlay ModSelect { get; private set; }
|
||||||
|
|
||||||
@ -297,7 +299,7 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
||||||
{
|
{
|
||||||
Task.Run(() => stableImportManager.ImportFromStableAsync(StableContent.All));
|
Task.Run(() => legacyImportManager.ImportFromStableAsync(StableContent.All));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -24,9 +24,6 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
protected override IEnumerable<string> GetFilenames(string name)
|
protected override IEnumerable<string> GetFilenames(string name)
|
||||||
{
|
{
|
||||||
if (source.Files == null)
|
|
||||||
yield break;
|
|
||||||
|
|
||||||
foreach (string filename in base.GetFilenames(name))
|
foreach (string filename in base.GetFilenames(name))
|
||||||
{
|
{
|
||||||
string path = getPathForFile(filename.ToStandardisedPath());
|
string path = getPathForFile(filename.ToStandardisedPath());
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Skinning
|
|||||||
string filename = $"{skinnableTarget}.json";
|
string filename = $"{skinnableTarget}.json";
|
||||||
|
|
||||||
// skininfo files may be null for default skin.
|
// skininfo files may be null for default skin.
|
||||||
var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == filename);
|
var fileInfo = SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
|
||||||
|
|
||||||
if (fileInfo == null)
|
if (fileInfo == null)
|
||||||
continue;
|
continue;
|
||||||
|
@ -7,7 +7,7 @@ using osu.Game.IO;
|
|||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey
|
public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage
|
||||||
{
|
{
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
|
|
||||||
@ -19,5 +19,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
|
|
||||||
|
IFileInfo INamedFileUsage.File => FileInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.IO;
|
|||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class SkinInfo : IHasFiles<SkinFileInfo>, IEquatable<SkinInfo>, IHasPrimaryKey, ISoftDelete
|
public class SkinInfo : IHasFiles<SkinFileInfo>, IEquatable<SkinInfo>, IHasPrimaryKey, ISoftDelete, IHasNamedFiles
|
||||||
{
|
{
|
||||||
internal const int DEFAULT_SKIN = 0;
|
internal const int DEFAULT_SKIN = 0;
|
||||||
internal const int CLASSIC_SKIN = -1;
|
internal const int CLASSIC_SKIN = -1;
|
||||||
@ -36,7 +36,7 @@ namespace osu.Game.Skinning
|
|||||||
return (Skin)Activator.CreateInstance(type, this, resources);
|
return (Skin)Activator.CreateInstance(type, this, resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SkinFileInfo> Files { get; set; } = new List<SkinFileInfo>();
|
public List<SkinFileInfo> Files { get; } = new List<SkinFileInfo>();
|
||||||
|
|
||||||
public bool DeletePending { get; set; }
|
public bool DeletePending { get; set; }
|
||||||
|
|
||||||
@ -55,5 +55,7 @@ namespace osu.Game.Skinning
|
|||||||
string author = Creator == null ? string.Empty : $"({Creator})";
|
string author = Creator == null ? string.Empty : $"({Creator})";
|
||||||
return $"{Name} {author}".Trim();
|
return $"{Name} {author}".Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => Files;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,15 +18,14 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
@ -38,7 +37,7 @@ namespace osu.Game.Skinning
|
|||||||
/// For gameplay components, see <see cref="RulesetSkinProvidingContainer"/> which adds extra legacy and toggle logic that may affect the lookup process.
|
/// For gameplay components, see <see cref="RulesetSkinProvidingContainer"/> which adds extra legacy and toggle logic that may affect the lookup process.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource, IStorageResourceProvider
|
public class SkinManager : ISkinSource, IStorageResourceProvider, IModelImporter<SkinInfo>, IModelManager<SkinInfo>
|
||||||
{
|
{
|
||||||
private readonly AudioManager audio;
|
private readonly AudioManager audio;
|
||||||
|
|
||||||
@ -49,11 +48,11 @@ namespace osu.Game.Skinning
|
|||||||
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>();
|
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>();
|
||||||
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
|
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
|
||||||
|
|
||||||
public override IEnumerable<string> HandledExtensions => new[] { ".osk" };
|
private readonly SkinModelManager skinModelManager;
|
||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".ini", ".json" };
|
private readonly SkinStore skinStore;
|
||||||
|
|
||||||
protected override string ImportFromStablePath => "Skins";
|
private readonly IResourceStore<byte[]> userFiles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default skin.
|
/// The default skin.
|
||||||
@ -66,12 +65,16 @@ namespace osu.Game.Skinning
|
|||||||
public Skin DefaultLegacySkin { get; }
|
public Skin DefaultLegacySkin { get; }
|
||||||
|
|
||||||
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore<byte[]> resources, AudioManager audio)
|
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore<byte[]> resources, AudioManager audio)
|
||||||
: base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
|
|
||||||
{
|
{
|
||||||
this.audio = audio;
|
this.audio = audio;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
|
|
||||||
|
skinStore = new SkinStore(contextFactory, storage);
|
||||||
|
userFiles = new FileStore(contextFactory, storage).Store;
|
||||||
|
|
||||||
|
skinModelManager = new SkinModelManager(storage, contextFactory, skinStore, host, this);
|
||||||
|
|
||||||
DefaultLegacySkin = new DefaultLegacySkin(this);
|
DefaultLegacySkin = new DefaultLegacySkin(this);
|
||||||
DefaultSkin = new DefaultSkin(this);
|
DefaultSkin = new DefaultSkin(this);
|
||||||
|
|
||||||
@ -85,31 +88,8 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
SourceChanged?.Invoke();
|
SourceChanged?.Invoke();
|
||||||
};
|
};
|
||||||
|
|
||||||
// can be removed 20220420.
|
|
||||||
populateMissingHashes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateMissingHashes()
|
|
||||||
{
|
|
||||||
var skinsWithoutHashes = ModelStore.ConsumableItems.Where(i => i.Hash == null).ToArray();
|
|
||||||
|
|
||||||
foreach (SkinInfo skin in skinsWithoutHashes)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Update(skin);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Delete(skin);
|
|
||||||
Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
|
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -129,15 +109,15 @@ namespace osu.Game.Skinning
|
|||||||
public List<SkinInfo> GetAllUserSkins(bool includeFiles = false)
|
public List<SkinInfo> GetAllUserSkins(bool includeFiles = false)
|
||||||
{
|
{
|
||||||
if (includeFiles)
|
if (includeFiles)
|
||||||
return ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
return skinStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
||||||
|
|
||||||
return ModelStore.Items.Where(s => !s.DeletePending).ToList();
|
return skinStore.Items.Where(s => !s.DeletePending).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectRandomSkin()
|
public void SelectRandomSkin()
|
||||||
{
|
{
|
||||||
// choose from only user skins, removing the current selection to ensure a new one is chosen.
|
// choose from only user skins, removing the current selection to ensure a new one is chosen.
|
||||||
var randomChoices = ModelStore.Items.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
|
var randomChoices = skinStore.Items.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
|
||||||
|
|
||||||
if (randomChoices.Length == 0)
|
if (randomChoices.Length == 0)
|
||||||
{
|
{
|
||||||
@ -146,137 +126,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
|
var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
|
||||||
CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID);
|
CurrentSkinInfo.Value = skinStore.ConsumableItems.Single(i => i.ID == chosen.ID);
|
||||||
}
|
|
||||||
|
|
||||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" };
|
|
||||||
|
|
||||||
private const string unknown_creator_string = @"Unknown";
|
|
||||||
|
|
||||||
protected override bool HasCustomHashFunction => true;
|
|
||||||
|
|
||||||
protected override string ComputeHash(SkinInfo item)
|
|
||||||
{
|
|
||||||
var instance = GetSkin(item);
|
|
||||||
|
|
||||||
// This function can be run on fresh import or save. The logic here ensures a skin.ini file is in a good state for both operations.
|
|
||||||
|
|
||||||
// `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above.
|
|
||||||
string skinIniSourcedName = instance.Configuration.SkinInfo.Name;
|
|
||||||
string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator;
|
|
||||||
string archiveName = item.Name.Replace(@".osk", string.Empty, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
bool isImport = item.ID == 0;
|
|
||||||
|
|
||||||
if (isImport)
|
|
||||||
{
|
|
||||||
item.Name = !string.IsNullOrEmpty(skinIniSourcedName) ? skinIniSourcedName : archiveName;
|
|
||||||
item.Creator = !string.IsNullOrEmpty(skinIniSourcedCreator) ? skinIniSourcedCreator : unknown_creator_string;
|
|
||||||
|
|
||||||
// For imports, we want to use the archive or folder name as part of the metadata, in addition to any existing skin.ini metadata.
|
|
||||||
// In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications.
|
|
||||||
// In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin.
|
|
||||||
if (archiveName != item.Name)
|
|
||||||
item.Name = @$"{item.Name} [{archiveName}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
// By this point, the metadata in SkinInfo will be correct.
|
|
||||||
// Regardless of whether this is an import or not, let's write the skin.ini if non-existing or non-matching.
|
|
||||||
// This is (weirdly) done inside ComputeHash to avoid adding a new method to handle this case. After switching to realm it can be moved into another place.
|
|
||||||
if (skinIniSourcedName != item.Name)
|
|
||||||
updateSkinIniMetadata(item);
|
|
||||||
|
|
||||||
return base.ComputeHash(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSkinIniMetadata(SkinInfo item)
|
|
||||||
{
|
|
||||||
string nameLine = @$"Name: {item.Name}";
|
|
||||||
string authorLine = @$"Author: {item.Creator}";
|
|
||||||
|
|
||||||
string[] newLines =
|
|
||||||
{
|
|
||||||
@"// The following content was automatically added by osu! during import, based on filename / folder metadata.",
|
|
||||||
@"[General]",
|
|
||||||
nameLine,
|
|
||||||
authorLine,
|
|
||||||
};
|
|
||||||
|
|
||||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (existingFile == null)
|
|
||||||
{
|
|
||||||
// In the case a skin doesn't have a skin.ini yet, let's create one.
|
|
||||||
writeNewSkinIni();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (Stream stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
|
||||||
{
|
|
||||||
using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.GetStoragePath()))
|
|
||||||
using (var sr = new StreamReader(existingStream))
|
|
||||||
{
|
|
||||||
string line;
|
|
||||||
while ((line = sr.ReadLine()) != null)
|
|
||||||
sw.WriteLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.WriteLine();
|
|
||||||
|
|
||||||
foreach (string line in newLines)
|
|
||||||
sw.WriteLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplaceFile(item, existingFile, stream);
|
|
||||||
|
|
||||||
// can be removed 20220502.
|
|
||||||
if (!ensureIniWasUpdated(item))
|
|
||||||
{
|
|
||||||
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
|
|
||||||
|
|
||||||
DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)));
|
|
||||||
writeNewSkinIni();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeNewSkinIni()
|
|
||||||
{
|
|
||||||
using (Stream stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
|
||||||
{
|
|
||||||
foreach (string line in newLines)
|
|
||||||
sw.WriteLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddFile(item, stream, @"skin.ini");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ensureIniWasUpdated(SkinInfo item)
|
|
||||||
{
|
|
||||||
// This is a final consistency check to ensure that hash computation doesn't enter an infinite loop.
|
|
||||||
// With other changes to the surrounding code this should never be hit, but until we are 101% sure that there
|
|
||||||
// are no other cases let's avoid a hard startup crash by bailing and alerting.
|
|
||||||
|
|
||||||
var instance = GetSkin(item);
|
|
||||||
|
|
||||||
return instance.Configuration.SkinInfo.Name == item.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var instance = GetSkin(model);
|
|
||||||
|
|
||||||
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
|
||||||
|
|
||||||
model.Name = instance.Configuration.SkinInfo.Name;
|
|
||||||
model.Creator = instance.Configuration.SkinInfo.Creator;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -297,7 +147,7 @@ namespace osu.Game.Skinning
|
|||||||
var skin = CurrentSkin.Value;
|
var skin = CurrentSkin.Value;
|
||||||
|
|
||||||
// if the user is attempting to save one of the default skin implementations, create a copy first.
|
// if the user is attempting to save one of the default skin implementations, create a copy first.
|
||||||
CurrentSkinInfo.Value = Import(new SkinInfo
|
CurrentSkinInfo.Value = skinModelManager.Import(new SkinInfo
|
||||||
{
|
{
|
||||||
Name = skin.SkinInfo.Name + @" (modified)",
|
Name = skin.SkinInfo.Name + @" (modified)",
|
||||||
Creator = skin.SkinInfo.Creator,
|
Creator = skin.SkinInfo.Creator,
|
||||||
@ -321,9 +171,9 @@ namespace osu.Game.Skinning
|
|||||||
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
|
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
|
||||||
|
|
||||||
if (oldFile != null)
|
if (oldFile != null)
|
||||||
ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename);
|
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename);
|
||||||
else
|
else
|
||||||
AddFile(skin.SkinInfo, streamContent, filename);
|
skinModelManager.AddFile(skin.SkinInfo, streamContent, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +183,7 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="query">The query.</param>
|
/// <param name="query">The query.</param>
|
||||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||||
public SkinInfo Query(Expression<Func<SkinInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
|
public SkinInfo Query(Expression<Func<SkinInfo, bool>> query) => skinStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
|
||||||
|
|
||||||
public event Action SourceChanged;
|
public event Action SourceChanged;
|
||||||
|
|
||||||
@ -386,9 +236,101 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
AudioManager IStorageResourceProvider.AudioManager => audio;
|
AudioManager IStorageResourceProvider.AudioManager => audio;
|
||||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||||
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
IResourceStore<byte[]> IStorageResourceProvider.Files => userFiles;
|
||||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
|
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IModelImporter<SkinInfo>
|
||||||
|
|
||||||
|
public Action<Notification> PostNotification
|
||||||
|
{
|
||||||
|
set => skinModelManager.PostNotification = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<IEnumerable<ILive<SkinInfo>>> PostImport
|
||||||
|
{
|
||||||
|
set => skinModelManager.PostImport = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Import(params string[] paths)
|
||||||
|
{
|
||||||
|
return skinModelManager.Import(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Import(params ImportTask[] tasks)
|
||||||
|
{
|
||||||
|
return skinModelManager.Import(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> HandledExtensions => skinModelManager.HandledExtensions;
|
||||||
|
|
||||||
|
public Task<IEnumerable<ILive<SkinInfo>>> Import(ProgressNotification notification, params ImportTask[] tasks)
|
||||||
|
{
|
||||||
|
return skinModelManager.Import(notification, tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ILive<SkinInfo>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return skinModelManager.Import(task, lowPriority, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ILive<SkinInfo>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return skinModelManager.Import(archive, lowPriority, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ILive<SkinInfo>> Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return skinModelManager.Import(item, archive, lowPriority, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IModelManager<SkinInfo>
|
||||||
|
|
||||||
|
public event Action<SkinInfo> ItemUpdated
|
||||||
|
{
|
||||||
|
add => skinModelManager.ItemUpdated += value;
|
||||||
|
remove => skinModelManager.ItemUpdated -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<SkinInfo> ItemRemoved
|
||||||
|
{
|
||||||
|
add => skinModelManager.ItemRemoved += value;
|
||||||
|
remove => skinModelManager.ItemRemoved -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(SkinInfo item)
|
||||||
|
{
|
||||||
|
skinModelManager.Update(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete(SkinInfo item)
|
||||||
|
{
|
||||||
|
return skinModelManager.Delete(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(List<SkinInfo> items, bool silent = false)
|
||||||
|
{
|
||||||
|
skinModelManager.Delete(items, silent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Undelete(List<SkinInfo> items, bool silent = false)
|
||||||
|
{
|
||||||
|
skinModelManager.Undelete(items, silent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Undelete(SkinInfo item)
|
||||||
|
{
|
||||||
|
skinModelManager.Undelete(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAvailableLocally(SkinInfo model)
|
||||||
|
{
|
||||||
|
return skinModelManager.IsAvailableLocally(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
189
osu.Game/Skinning/SkinModelManager.cs
Normal file
189
osu.Game/Skinning/SkinModelManager.cs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.IO.Archives;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
public class SkinModelManager : ArchiveModelManager<SkinInfo, SkinFileInfo>
|
||||||
|
{
|
||||||
|
private readonly IStorageResourceProvider skinResources;
|
||||||
|
|
||||||
|
public SkinModelManager(Storage storage, DatabaseContextFactory contextFactory, SkinStore skinStore, GameHost host, IStorageResourceProvider skinResources)
|
||||||
|
: base(storage, contextFactory, skinStore, host)
|
||||||
|
{
|
||||||
|
this.skinResources = skinResources;
|
||||||
|
|
||||||
|
// can be removed 20220420.
|
||||||
|
populateMissingHashes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<string> HandledExtensions => new[] { ".osk" };
|
||||||
|
|
||||||
|
protected override string[] HashableFileTypes => new[] { ".ini", ".json" };
|
||||||
|
|
||||||
|
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk";
|
||||||
|
|
||||||
|
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" };
|
||||||
|
|
||||||
|
private const string unknown_creator_string = @"Unknown";
|
||||||
|
|
||||||
|
protected override bool HasCustomHashFunction => true;
|
||||||
|
|
||||||
|
protected override string ComputeHash(SkinInfo item)
|
||||||
|
{
|
||||||
|
var instance = createInstance(item);
|
||||||
|
|
||||||
|
// This function can be run on fresh import or save. The logic here ensures a skin.ini file is in a good state for both operations.
|
||||||
|
|
||||||
|
// `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above.
|
||||||
|
string skinIniSourcedName = instance.Configuration.SkinInfo.Name;
|
||||||
|
string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator;
|
||||||
|
string archiveName = item.Name.Replace(@".osk", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
bool isImport = item.ID == 0;
|
||||||
|
|
||||||
|
if (isImport)
|
||||||
|
{
|
||||||
|
item.Name = !string.IsNullOrEmpty(skinIniSourcedName) ? skinIniSourcedName : archiveName;
|
||||||
|
item.Creator = !string.IsNullOrEmpty(skinIniSourcedCreator) ? skinIniSourcedCreator : unknown_creator_string;
|
||||||
|
|
||||||
|
// For imports, we want to use the archive or folder name as part of the metadata, in addition to any existing skin.ini metadata.
|
||||||
|
// In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications.
|
||||||
|
// In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin.
|
||||||
|
if (archiveName != item.Name)
|
||||||
|
item.Name = @$"{item.Name} [{archiveName}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// By this point, the metadata in SkinInfo will be correct.
|
||||||
|
// Regardless of whether this is an import or not, let's write the skin.ini if non-existing or non-matching.
|
||||||
|
// This is (weirdly) done inside ComputeHash to avoid adding a new method to handle this case. After switching to realm it can be moved into another place.
|
||||||
|
if (skinIniSourcedName != item.Name)
|
||||||
|
updateSkinIniMetadata(item);
|
||||||
|
|
||||||
|
return base.ComputeHash(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSkinIniMetadata(SkinInfo item)
|
||||||
|
{
|
||||||
|
string nameLine = @$"Name: {item.Name}";
|
||||||
|
string authorLine = @$"Author: {item.Creator}";
|
||||||
|
|
||||||
|
string[] newLines =
|
||||||
|
{
|
||||||
|
@"// The following content was automatically added by osu! during import, based on filename / folder metadata.",
|
||||||
|
@"[General]",
|
||||||
|
nameLine,
|
||||||
|
authorLine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (existingFile == null)
|
||||||
|
{
|
||||||
|
// In the case a skin doesn't have a skin.ini yet, let's create one.
|
||||||
|
writeNewSkinIni();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (Stream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
|
{
|
||||||
|
using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.GetStoragePath()))
|
||||||
|
using (var sr = new StreamReader(existingStream))
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
while ((line = sr.ReadLine()) != null)
|
||||||
|
sw.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.WriteLine();
|
||||||
|
|
||||||
|
foreach (string line in newLines)
|
||||||
|
sw.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplaceFile(item, existingFile, stream);
|
||||||
|
|
||||||
|
// can be removed 20220502.
|
||||||
|
if (!ensureIniWasUpdated(item))
|
||||||
|
{
|
||||||
|
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
|
||||||
|
|
||||||
|
DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
writeNewSkinIni();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeNewSkinIni()
|
||||||
|
{
|
||||||
|
using (Stream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
|
{
|
||||||
|
foreach (string line in newLines)
|
||||||
|
sw.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFile(item, stream, @"skin.ini");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ensureIniWasUpdated(SkinInfo item)
|
||||||
|
{
|
||||||
|
// This is a final consistency check to ensure that hash computation doesn't enter an infinite loop.
|
||||||
|
// With other changes to the surrounding code this should never be hit, but until we are 101% sure that there
|
||||||
|
// are no other cases let's avoid a hard startup crash by bailing and alerting.
|
||||||
|
|
||||||
|
var instance = createInstance(item);
|
||||||
|
|
||||||
|
return instance.Configuration.SkinInfo.Name == item.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var instance = createInstance(model);
|
||||||
|
|
||||||
|
model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo();
|
||||||
|
|
||||||
|
model.Name = instance.Configuration.SkinInfo.Name;
|
||||||
|
model.Creator = instance.Configuration.SkinInfo.Creator;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateMissingHashes()
|
||||||
|
{
|
||||||
|
var skinsWithoutHashes = ModelStore.ConsumableItems.Where(i => i.Hash == null).ToArray();
|
||||||
|
|
||||||
|
foreach (SkinInfo skin in skinsWithoutHashes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Update(skin);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Delete(skin);
|
||||||
|
Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources);
|
||||||
|
}
|
||||||
|
}
|
@ -253,7 +253,7 @@ namespace osu.Game.Stores
|
|||||||
var scheduledImport = Task.Factory.StartNew(async () => await Import(model, archive, lowPriority, cancellationToken).ConfigureAwait(false),
|
var scheduledImport = Task.Factory.StartNew(async () => await Import(model, archive, lowPriority, cancellationToken).ConfigureAwait(false),
|
||||||
cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap();
|
cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap();
|
||||||
|
|
||||||
return await scheduledImport.ConfigureAwait(true);
|
return await scheduledImport.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -86,9 +86,13 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
var realm = realmFactory.Context;
|
Logger.Log(@"Beginning realm file store cleanup");
|
||||||
|
|
||||||
|
int totalFiles = 0;
|
||||||
|
int removedFiles = 0;
|
||||||
|
|
||||||
// can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal.
|
// can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal.
|
||||||
|
using (var realm = realmFactory.CreateContext())
|
||||||
using (var transaction = realm.BeginWrite())
|
using (var transaction = realm.BeginWrite())
|
||||||
{
|
{
|
||||||
// TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707)
|
// TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707)
|
||||||
@ -96,11 +100,14 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
|
totalFiles++;
|
||||||
|
|
||||||
if (file.BacklinksCount > 0)
|
if (file.BacklinksCount > 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
removedFiles++;
|
||||||
Storage.Delete(file.GetStoragePath());
|
Storage.Delete(file.GetStoragePath());
|
||||||
realm.Remove(file);
|
realm.Remove(file);
|
||||||
}
|
}
|
||||||
@ -112,6 +119,8 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,23 +87,19 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
{
|
{
|
||||||
AddStep("setup skins", () =>
|
AddStep("setup skins", () =>
|
||||||
{
|
{
|
||||||
userSkinInfo.Files = new List<SkinFileInfo>
|
userSkinInfo.Files.Clear();
|
||||||
|
userSkinInfo.Files.Add(new SkinFileInfo
|
||||||
{
|
{
|
||||||
new SkinFileInfo
|
Filename = userFile,
|
||||||
{
|
FileInfo = new IO.FileInfo { Hash = userFile }
|
||||||
Filename = userFile,
|
});
|
||||||
FileInfo = new IO.FileInfo { Hash = userFile }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beatmapInfo.BeatmapSet.Files = new List<BeatmapSetFileInfo>
|
beatmapInfo.BeatmapSet.Files.Clear();
|
||||||
|
beatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo
|
||||||
{
|
{
|
||||||
new BeatmapSetFileInfo
|
Filename = beatmapFile,
|
||||||
{
|
FileInfo = new IO.FileInfo { Hash = beatmapFile }
|
||||||
Filename = beatmapFile,
|
});
|
||||||
FileInfo = new IO.FileInfo { Hash = beatmapFile }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Need to refresh the cached skin source to refresh the skin resource store.
|
// Need to refresh the cached skin source to refresh the skin resource store.
|
||||||
dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this));
|
dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this));
|
||||||
|
@ -299,7 +299,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return ((IMultiplayerClient)this).LoadRequested();
|
return ((IMultiplayerClient)this).LoadRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task AddPlaylistItem(MultiplayerPlaylistItem item)
|
public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item)
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
Debug.Assert(APIRoom != null);
|
Debug.Assert(APIRoom != null);
|
||||||
@ -313,6 +313,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
case QueueMode.HostOnly:
|
case QueueMode.HostOnly:
|
||||||
// In host-only mode, the current item is re-used.
|
// In host-only mode, the current item is re-used.
|
||||||
item.ID = currentItem.ID;
|
item.ID = currentItem.ID;
|
||||||
|
item.OwnerID = currentItem.OwnerID;
|
||||||
|
|
||||||
serverSidePlaylist[currentIndex] = item;
|
serverSidePlaylist[currentIndex] = item;
|
||||||
await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false);
|
await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false);
|
||||||
@ -323,6 +324,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
item.ID = serverSidePlaylist.Last().ID + 1;
|
item.ID = serverSidePlaylist.Last().ID + 1;
|
||||||
|
item.OwnerID = userId;
|
||||||
|
|
||||||
serverSidePlaylist.Add(item);
|
serverSidePlaylist.Add(item);
|
||||||
await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
|
await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
|
||||||
@ -332,6 +334,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item);
|
||||||
|
|
||||||
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist)
|
IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
@ -55,6 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
|
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="room">The room.</param>
|
/// <param name="room">The room.</param>
|
||||||
public void AddServerSideRoom(Room room) => requestsHandler.AddServerSideRoom(room);
|
/// <param name="host">The host.</param>
|
||||||
|
public void AddServerSideRoom(Room room, APIUser host) => requestsHandler.AddServerSideRoom(room, host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
|
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
|
||||||
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
|
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
|
||||||
|
|
||||||
AddServerSideRoom(apiRoom);
|
AddServerSideRoom(apiRoom, localUser);
|
||||||
|
|
||||||
var responseRoom = new APICreatedRoom();
|
var responseRoom = new APICreatedRoom();
|
||||||
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
|
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
|
||||||
@ -125,11 +125,17 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
|
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="room">The room.</param>
|
/// <param name="room">The room.</param>
|
||||||
public void AddServerSideRoom(Room room)
|
/// <param name="host">The room host.</param>
|
||||||
|
public void AddServerSideRoom(Room room, APIUser host)
|
||||||
{
|
{
|
||||||
room.RoomID.Value ??= currentRoomId++;
|
room.RoomID.Value ??= currentRoomId++;
|
||||||
|
room.Host.Value = host;
|
||||||
|
|
||||||
for (int i = 0; i < room.Playlist.Count; i++)
|
for (int i = 0; i < room.Playlist.Count; i++)
|
||||||
|
{
|
||||||
room.Playlist[i].ID = currentPlaylistItemId++;
|
room.Playlist[i].ID = currentPlaylistItemId++;
|
||||||
|
room.Playlist[i].OwnerID = room.Host.Value.OnlineID;
|
||||||
|
}
|
||||||
|
|
||||||
serverSideRooms.Add(room);
|
serverSideRooms.Add(room);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user