1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 21:17:33 +08:00

Merge remote-tracking branch 'origin/positional-sounds-strength-adjustment' into positional-sounds-strength-adjustment

This commit is contained in:
mk-56 2021-11-28 03:25:34 +01:00
commit 74a16f233f
99 changed files with 763 additions and 1386 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1124.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.1127.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -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()
{ {

View File

@ -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;

View File

@ -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);

View File

@ -70,6 +70,11 @@ namespace osu.Game.Tests.Visual.Beatmaps
AddWaitStep("wait some", 3); AddWaitStep("wait some", 3);
AddAssert("button still visible", () => playButton.IsPresent); 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("wait for track to end", () => !playButton.Playing.Value);
AddUntilStep("button hidden", () => !playButton.IsPresent); AddUntilStep("button hidden", () => !playButton.IsPresent);
} }

View File

@ -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));

View File

@ -93,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);
} }

View File

@ -13,15 +13,16 @@ 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;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; 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 +35,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)
{ {
@ -231,7 +235,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertDownloadButtonVisible(false); assertDownloadButtonVisible(false);
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}", void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
() => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().Single().Alpha == (visible ? 1 : 0)); () => playlist.ChildrenOfType<BeatmapDownloadButton>().Single().Alpha == (visible ? 1 : 0));
} }
[Test] [Test]
@ -245,7 +249,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
createPlaylist(byOnlineId, byChecksum); createPlaylist(byOnlineId, byChecksum);
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().All(d => d.IsPresent)); AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadButton>().All(d => d.IsPresent));
} }
[Test] [Test]
@ -304,6 +308,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 +340,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 +356,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 +404,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 +424,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)
{ {
} }
} }

View File

@ -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()));

View File

@ -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());

View File

@ -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]

View File

@ -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]

View File

@ -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());

View File

@ -6,15 +6,15 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
public class TestSceneDirectDownloadButton : OsuTestScene public class TestSceneBeatmapDownloadButton : OsuTestScene
{ {
private TestDownloadButton downloadButton; private TestDownloadButton downloadButton;
@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Online
return apiBeatmapSet; return apiBeatmapSet;
} }
private class TestDownloadButton : BeatmapPanelDownloadButton private class TestDownloadButton : BeatmapDownloadButton
{ {
public new bool DownloadEnabled => base.DownloadEnabled; public new bool DownloadEnabled => base.DownloadEnabled;

View File

@ -1,137 +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 System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osuTK;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tests.Visual.Online
{
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner
{
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var normal = getBeatmapSet();
normal.HasVideo = true;
normal.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet();
var explicitMap = getBeatmapSet();
explicitMap.HasExplicitContent = true;
var featuredMap = getBeatmapSet();
featuredMap.TrackId = 1;
var explicitFeaturedMap = getBeatmapSet();
explicitFeaturedMap.HasExplicitContent = true;
explicitFeaturedMap.TrackId = 2;
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
Padding = new MarginPadding(20),
Spacing = new Vector2(5, 20),
Children = new Drawable[]
{
new GridBeatmapPanel(normal),
new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties),
new GridBeatmapPanel(explicitMap),
new GridBeatmapPanel(featuredMap),
new GridBeatmapPanel(explicitFeaturedMap),
new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties),
new ListBeatmapPanel(explicitMap),
new ListBeatmapPanel(featuredMap),
new ListBeatmapPanel(explicitFeaturedMap)
},
},
};
APIBeatmapSet getBeatmapSet() => CreateAPIBeatmapSet(Ruleset.Value);
APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
{
OnlineID = 123,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new APIUser
{
Username = "BanchoBot",
Id = 3,
},
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new[]
{
new APIBeatmap
{
RulesetID = Ruleset.Value.OnlineID,
DifficultyName = "Test",
StarRating = 6.42,
}
}
};
APIBeatmapSet getManyDifficultiesBeatmapSet()
{
var beatmaps = new List<APIBeatmap>();
for (int i = 0; i < 100; i++)
{
beatmaps.Add(new APIBeatmap
{
RulesetID = i % 4,
StarRating = 2 + i % 4 * 2,
OverallDifficulty = 3.5f,
});
}
return new APIBeatmapSet
{
OnlineID = 1,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new APIUser
{
Username = "BanchoBot",
Id = 3,
},
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = beatmaps.ToArray(),
};
}
}
}
}

View File

@ -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;
} }

View File

@ -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];

View File

@ -105,6 +105,8 @@ namespace osu.Game.Audio
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
if (Owner == null)
Logger.Log($"A {nameof(PreviewTrack)} was created without a containing {nameof(IPreviewTrackOwner)}. An owner should be added for correct behaviour."); Logger.Log($"A {nameof(PreviewTrack)} was created without a containing {nameof(IPreviewTrackOwner)}. An owner should be added for correct behaviour.");
} }

View File

@ -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)

View File

@ -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)
{ {
} }
} }

View File

@ -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();

View File

@ -95,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
} }

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -14,9 +13,9 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.BeatmapListing.Panels namespace osu.Game.Beatmaps.Drawables
{ {
public class BeatmapPanelDownloadButton : CompositeDrawable public class BeatmapDownloadButton : CompositeDrawable
{ {
protected bool DownloadEnabled => button.Enabled.Value; protected bool DownloadEnabled => button.Enabled.Value;
@ -35,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
private readonly IBeatmapSetInfo beatmapSet; private readonly IBeatmapSetInfo beatmapSet;
public BeatmapPanelDownloadButton(IBeatmapSetInfo beatmapSet) public BeatmapDownloadButton(IBeatmapSetInfo beatmapSet)
{ {
this.beatmapSet = beatmapSet; this.beatmapSet = beatmapSet;
@ -65,7 +64,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);

View File

@ -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;
@ -21,7 +23,6 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osuTK; using osuTK;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton;
@ -41,27 +42,23 @@ namespace osu.Game.Beatmaps.Drawables.Cards
private readonly BeatmapDownloadTracker downloadTracker; private readonly BeatmapDownloadTracker downloadTracker;
private BeatmapCardThumbnail thumbnail; 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)
@ -71,14 +68,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,
@ -335,6 +336,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()

View File

@ -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;

View File

@ -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,8 +56,23 @@ 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)
{
case DownloadState.Downloading:
case DownloadState.Importing:
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) if (beatmapSet.Availability.DownloadDisabled)
{ {
Enabled.Value = false; Enabled.Value = false;
@ -58,12 +80,20 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
return; return;
} }
Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value);
this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
spinner.Hide();
Icon.Show();
if (!beatmapSet.HasVideo) if (!beatmapSet.HasVideo)
TooltipText = BeatmapsetsStrings.PanelDownloadAll; TooltipText = BeatmapsetsStrings.PanelDownloadAll;
else else
TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo;
break;
Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); default:
throw new InvalidOperationException($"Unknown {nameof(DownloadState)} specified.");
}
} }
} }
} }

View File

@ -97,6 +97,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
if (previewTrack == null) if (previewTrack == null)
{ {
toggleLoading(true); toggleLoading(true);
LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded); LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded);
} }
else else
@ -111,6 +112,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
} }
private void onPreviewLoaded(PreviewTrack loadedPreview) private void onPreviewLoaded(PreviewTrack loadedPreview)
{
// Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution.
// See: https://github.com/ppy/osu-framework/issues/4692
Schedule(() =>
{ {
// another async load might have completed before this one. // another async load might have completed before this one.
// if so, do not make any changes. // if so, do not make any changes.
@ -124,6 +129,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
if (Playing.Value) if (Playing.Value)
tryStartPreview(); tryStartPreview();
});
} }
private void tryStartPreview() private void tryStartPreview()

View File

@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels namespace osu.Game.Beatmaps.Drawables.Cards
{ {
public class IconPill : CircularContainer public class IconPill : CircularContainer
{ {

View File

@ -5,13 +5,12 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels namespace osu.Game.Beatmaps.Drawables
{ {
public class DownloadProgressBar : CompositeDrawable public class DownloadProgressBar : CompositeDrawable
{ {

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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)
@ -452,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>
@ -728,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>
@ -746,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>
@ -909,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;
}
} }
} }

View File

@ -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;
} }
} }

View 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; }
}
}

View File

@ -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>

View File

@ -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.

View File

@ -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>

View 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)
{
}
}
}

View 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)
{
}
}
}

View 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);
}
}
}
}

View File

@ -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);
} }

View 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;
}
}

View 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);
}
}
}

View 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)
{
}
}
}

View 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)
{
}
}
}

View 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)
{
}
}
}

View File

@ -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;

View File

@ -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;
if (data.IsManaged)
{
IsManaged = true;
fetchedContext = SynchronizationContext.Current; fetchedContext = SynchronizationContext.Current;
fetchedThreadId = Thread.CurrentThread.ManagedThreadId; 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;
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
} }
} }

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;
@ -395,7 +400,7 @@ namespace osu.Game.Online.Leaderboards
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))));

View File

@ -718,6 +718,7 @@ namespace osu.Game.Online.Multiplayer
var playlistItem = new PlaylistItem var playlistItem = new PlaylistItem
{ {
ID = item.ID, ID = item.ID,
OwnerID = item.OwnerID,
Beatmap = { Value = beatmap }, Beatmap = { Value = beatmap },
Ruleset = { Value = ruleset }, Ruleset = { Value = ruleset },
Expired = item.Expired Expired = item.Expired

View File

@ -18,6 +18,9 @@ namespace osu.Game.Online.Rooms
[JsonProperty("id")] [JsonProperty("id")]
public long ID { get; set; } public long ID { get; set; }
[JsonProperty("owner_id")]
public int OwnerID { get; set; }
[JsonProperty("beatmap_id")] [JsonProperty("beatmap_id")]
public int BeatmapID { get; set; } public int BeatmapID { get; set; }

View File

@ -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;
} }

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -1,216 +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 System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu
{
public readonly APIBeatmapSet SetInfo;
private const double hover_transition_time = 400;
private const int maximum_difficulty_icons = 10;
private Container content;
public PreviewTrack Preview => PlayButton.Preview;
public IBindable<bool> PreviewPlaying => PlayButton?.Playing;
protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; }
protected virtual bool FadePlayButton => true;
protected override Container<Drawable> Content => content;
protected Action ViewBeatmap;
protected BeatmapPanel(APIBeatmapSet setInfo)
: base(HoverSampleSet.Submit)
{
Debug.Assert(setInfo.OnlineID > 0);
SetInfo = setInfo;
}
private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 2f,
Colour = Color4.Black.Opacity(0.25f),
};
private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 5f),
Radius = 10f,
Colour = Color4.Black.Opacity(0.3f),
};
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay)
{
AddInternal(content = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
EdgeEffect = edgeEffectNormal,
Children = new[]
{
CreateBackground(),
new DownloadProgressBar(SetInfo)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Depth = -1,
},
}
});
Action = ViewBeatmap = () =>
{
Debug.Assert(SetInfo.OnlineID > 0);
beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineID);
};
}
protected override void Update()
{
base.Update();
if (PreviewPlaying.Value && Preview != null && Preview.TrackLoaded)
{
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
}
}
protected override bool OnHover(HoverEvent e)
{
content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint);
content.MoveToY(-4, hover_transition_time, Easing.OutQuint);
if (FadePlayButton)
PlayButton.FadeIn(120, Easing.InOutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint);
content.MoveToY(0, hover_transition_time, Easing.OutQuint);
if (FadePlayButton && !PreviewPlaying.Value)
PlayButton.FadeOut(120, Easing.InOutQuint);
base.OnHoverLost(e);
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
PreviewPlaying.ValueChanged += playing =>
{
PlayButton.FadeTo(playing.NewValue || IsHovered || !FadePlayButton ? 1 : 0, 120, Easing.InOutQuint);
PreviewBar.FadeTo(playing.NewValue ? 1 : 0, 120, Easing.InOutQuint);
};
}
protected List<DifficultyIcon> GetDifficultyIcons(OsuColour colours)
{
var icons = new List<DifficultyIcon>();
if (SetInfo.Beatmaps.Length > maximum_difficulty_icons)
{
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.Where(b => b.RulesetID == ruleset.OnlineID).ToList(), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
}
else
{
foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.RulesetID).ThenBy(beatmap => beatmap.StarRating))
icons.Add(new DifficultyIcon(b));
}
return icons;
}
protected Drawable CreateBackground() => new UpdateableOnlineBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
OnlineInfo = SetInfo,
};
public class Statistic : FillFlowContainer
{
private readonly SpriteText text;
private int value;
public int Value
{
get => value;
set
{
this.value = value;
text.Text = Value.ToString(@"N0");
}
}
public Statistic(IconUsage icon, int value = 0)
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(5f, 0f);
Children = new Drawable[]
{
text = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, italics: true) },
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = icon,
Shadow = true,
Size = new Vector2(14),
},
};
Value = value;
}
}
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("View Beatmap", MenuItemType.Highlighted, ViewBeatmap),
};
}
}

View File

@ -1,269 +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.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class GridBeatmapPanel : BeatmapPanel
{
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer;
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public GridBeatmapPanel(APIBeatmapSet beatmap)
: base(beatmap)
{
Width = 380;
Height = 140 + vertical_padding; // full height of all the elements plus vertical padding (autosize uses the image)
}
protected override void LoadComplete()
{
base.LoadComplete();
bottomPanel.LayoutDuration = 200;
bottomPanel.LayoutEasing = Easing.Out;
bottomPanel.Origin = Anchor.BottomLeft;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Content.CornerRadius = 4;
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
bottomPanel = new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, vertical_padding),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
titleContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
artistContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
}
}
}
},
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
},
progressBar = new Box
{
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Both,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding
{
Top = vertical_padding,
Bottom = vertical_padding,
Left = horizontal_padding,
Right = horizontal_padding,
},
Children = new Drawable[]
{
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.AddText("mapped by ", t => t.Colour = colours.Gray5);
d.AddUserLink(SetInfo.Author);
}),
new Container
{
AutoSizeAxes = Axes.X,
Height = 14,
Children = new[]
{
new OsuSpriteText
{
Text = SetInfo.Source,
Font = OsuFont.GetFont(size: 14),
Shadow = false,
Colour = colours.Gray5,
Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f,
},
},
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Spacing = new Vector2(3),
Children = GetDifficultyIcons(colours),
},
},
},
new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(50, 30),
Margin = new MarginPadding(horizontal_padding),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
},
},
},
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
Children = new[]
{
new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount),
new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount),
},
},
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5, Left = 5 },
Spacing = new Vector2(5),
},
playButton = new PlayButton(SetInfo)
{
Margin = new MarginPadding { Top = 5, Left = 10 },
Size = new Vector2(30),
Alpha = 0,
},
});
if (SetInfo.HasExplicitContent)
{
titleContainer.Add(new ExplicitContentBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.TrackId != null)
{
artistContainer.Add(new FeaturedArtistBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.HasVideo)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Film));
}
if (SetInfo.HasStoryboard)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Image));
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Status = SetInfo.Status,
});
PreviewPlaying.ValueChanged += _ => updateStatusContainer();
}
protected override bool OnHover(HoverEvent e)
{
updateStatusContainer();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
updateStatusContainer();
}
private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying.Value ? 0 : 1, 120, Easing.InOutQuint);
}
}

View File

@ -1,267 +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.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class ListBeatmapPanel : BeatmapPanel
{
private const float transition_duration = 120;
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private const float height = 70;
private FillFlowContainer statusContainer, titleContainer, artistContainer;
protected BeatmapPanelDownloadButton DownloadButton;
private PlayButton playButton;
private Box progressBar;
protected override bool FadePlayButton => false;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public ListBeatmapPanel(APIBeatmapSet beatmap)
: base(beatmap)
{
RelativeSizeAxes = Axes.X;
Height = height;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Content.CornerRadius = 5;
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding },
Children = new Drawable[]
{
new FillFlowContainer
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
LayoutDuration = transition_duration,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
playButton = new PlayButton(SetInfo)
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Size = new Vector2(height / 3),
FillMode = FillMode.Fit,
Margin = new MarginPadding { Right = 10 },
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
titleContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
artistContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
},
},
}
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 },
Spacing = new Vector2(5),
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Spacing = new Vector2(3),
Children = GetDifficultyIcons(colours),
},
},
},
},
},
}
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
Child = DownloadButton = new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(height - vertical_padding * 3),
Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding },
},
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount),
new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount),
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14);
})
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
}.With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.AddText("mapped by ");
d.AddUserLink(SetInfo.Author);
}),
new OsuSpriteText
{
Text = SetInfo.Source,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(size: 14),
Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f,
},
},
},
},
},
},
},
progressBar = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Y,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
});
if (SetInfo.HasExplicitContent)
{
titleContainer.Add(new ExplicitContentBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.TrackId != null)
{
artistContainer.Add(new FeaturedArtistBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.HasVideo)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
}
if (SetInfo.HasStoryboard)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 },
Status = SetInfo.Status,
});
}
}
}

View File

@ -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,

View File

@ -17,7 +17,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.BeatmapSet.Buttons;
using osuTK; using osuTK;
@ -286,7 +285,7 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
case DownloadState.LocallyAvailable: case DownloadState.LocallyAvailable:
// temporary for UX until new design is implemented. // temporary for UX until new design is implemented.
downloadButtonsContainer.Child = new BeatmapPanelDownloadButton(BeatmapSet.Value) downloadButtonsContainer.Child = new BeatmapDownloadButton(BeatmapSet.Value)
{ {
Width = 50, Width = 50,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,

View File

@ -9,13 +9,13 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
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;
@ -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;

View File

@ -14,7 +14,7 @@ using osu.Game.Online.API.Requests.Responses;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
public class PlayButton : Container public class PlayButton : Container
{ {

View File

@ -11,7 +11,6 @@ using osu.Game.Audio;
using osu.Game.Graphics; using osu.Game.Graphics;
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.Overlays.BeatmapListing.Panels;
using osuTK; using osuTK;
namespace osu.Game.Overlays.BeatmapSet.Buttons namespace osu.Game.Overlays.BeatmapSet.Buttons

View File

@ -214,7 +214,8 @@ namespace osu.Game.Overlays
{ {
base.LoadComplete(); base.LoadComplete();
beatmap.BindDisabledChanged(beatmapDisabledChanged, true); beatmap.BindDisabledChanged(_ => Scheduler.AddOnce(beatmapDisabledChanged));
beatmapDisabledChanged();
musicController.TrackChanged += trackChanged; musicController.TrackChanged += trackChanged;
trackChanged(beatmap.Value); trackChanged(beatmap.Value);
@ -318,8 +319,10 @@ namespace osu.Game.Overlays
}; };
} }
private void beatmapDisabledChanged(bool disabled) private void beatmapDisabledChanged()
{ {
bool disabled = beatmap.Disabled;
if (disabled) if (disabled)
playlist?.Hide(); playlist?.Hide();

View File

@ -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,

View File

@ -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,

View File

@ -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));
} }
}); });
} }
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, Text = MaintenanceSettingsStrings.DeleteAllBeatmaps,
Action = () => Action = () =>
{ {
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
{ {
deleteBeatmapsButton.Enabled.Value = false; deleteBeatmapsButton.Enabled.Value = false;
Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.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));
} }
}); });
} }
@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllScores, Text = MaintenanceSettingsStrings.DeleteAllScores,
Action = () => Action = () =>
{ {
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
{ {
deleteScoresButton.Enabled.Value = false; deleteScoresButton.Enabled.Value = false;
Task.Run(() => scores.Delete(scores.GetAllUsableScores())).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); Task.Run(() => scores.Delete(scores.GetAllUsableScores())).ContinueWith(t => Schedule(() => deleteScoresButton.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));
} }
}); });
} }
@ -103,7 +103,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllSkins, Text = MaintenanceSettingsStrings.DeleteAllSkins,
Action = () => Action = () =>
{ {
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
{ {
deleteSkinsButton.Enabled.Value = false; deleteSkinsButton.Enabled.Value = false;
Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.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));
} }
}); });
} }
@ -131,7 +131,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllCollections, Text = MaintenanceSettingsStrings.DeleteAllCollections,
Action = () => Action = () =>
{ {
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll));
} }
}); });
} }

View File

@ -7,9 +7,9 @@ using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.Maintenance namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class DeleteAllBeatmapsDialog : PopupDialog public class MassDeleteConfirmationDialog : PopupDialog
{ {
public DeleteAllBeatmapsDialog(Action deleteAction) public MassDeleteConfirmationDialog(Action deleteAction)
{ {
BodyText = "Everything?"; BodyText = "Everything?";

View File

@ -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)
{ {

View File

@ -68,13 +68,19 @@ namespace osu.Game.Overlays.Toolbar
{ {
base.LoadComplete(); base.LoadComplete();
Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true); Current.BindDisabledChanged(_ => Scheduler.AddOnce(currentDisabledChanged));
Current.BindValueChanged(_ => moveLineToCurrent()); currentDisabledChanged();
Current.BindValueChanged(_ => moveLineToCurrent());
// Scheduled to allow the button flow layout to be computed before the line position is updated // Scheduled to allow the button flow layout to be computed before the line position is updated
ScheduleAfterChildren(moveLineToCurrent); ScheduleAfterChildren(moveLineToCurrent);
} }
private void currentDisabledChanged()
{
this.FadeColour(Current.Disabled ? Color4.Gray : Color4.White, 300);
}
private bool hasInitialPosition; private bool hasInitialPosition;
private void moveLineToCurrent() private void moveLineToCurrent()

View File

@ -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; }

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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>, 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,30 +318,6 @@ namespace osu.Game.Scoring
#endregion #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

View File

@ -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)
{ {
} }

View File

@ -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));
} }
} }

View File

@ -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()

View File

@ -20,11 +20,13 @@ 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; ((ReversibleFillFlowContainer)ListContainer).Reverse = reverse;
} }
@ -56,7 +58,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

View File

@ -4,28 +4,32 @@
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;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.BeatmapSet; 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 +38,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 +50,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 +60,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 +80,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 +93,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 +143,14 @@ 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
},
} }
} }
}, },
@ -309,7 +338,7 @@ namespace osu.Game.Screens.OnlinePlay
return true; return true;
} }
private sealed class PlaylistDownloadButton : BeatmapPanelDownloadButton private sealed class PlaylistDownloadButton : BeatmapDownloadButton
{ {
private readonly PlaylistItem playlistItem; private readonly PlaylistItem playlistItem;
@ -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 ? string.Empty : $"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;
}
}
} }
} }

View File

@ -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)
{ {
} }

View File

@ -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,
}, },

View File

@ -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)

View File

@ -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:

View File

@ -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));
})); }));
} }
}); });

View File

@ -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;
} }
} }

View File

@ -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;
@ -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;
} }
} }

View File

@ -301,21 +301,6 @@ namespace osu.Game.Skinning
remove => skinModelManager.ItemRemoved -= value; remove => skinModelManager.ItemRemoved -= value;
} }
public Task ImportFromStableAsync(StableStorage stableStorage)
{
return skinModelManager.ImportFromStableAsync(stableStorage);
}
public void Export(SkinInfo item)
{
skinModelManager.Export(item);
}
public void ExportModelTo(SkinInfo model, Stream outputStream)
{
skinModelManager.ExportModelTo(model, outputStream);
}
public void Update(SkinInfo item) public void Update(SkinInfo item)
{ {
skinModelManager.Update(item); skinModelManager.Update(item);

View File

@ -34,8 +34,6 @@ namespace osu.Game.Skinning
protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; protected override string[] HashableFileTypes => new[] { ".ini", ".json" };
protected override string ImportFromStablePath => "Skins";
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk"; protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk";
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" }; protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" };

View File

@ -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>

View File

@ -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)

View File

@ -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);
} }
} }

View File

@ -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);
} }

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.7.1" /> <PackageReference Include="Realm" Version="10.7.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1124.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1127.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="Sentry" Version="3.11.1" /> <PackageReference Include="Sentry" Version="3.11.1" />
<PackageReference Include="SharpCompress" Version="0.30.0" /> <PackageReference Include="SharpCompress" Version="0.30.0" />

View File

@ -60,7 +60,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1124.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1127.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -83,7 +83,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1124.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1127.0" />
<PackageReference Include="SharpCompress" Version="0.30.0" /> <PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />