From 4011da033bba3c51cf591857f2fb14ad8c8e10d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 17:59:52 +0200 Subject: [PATCH 01/52] Split off thumbnail to separate component --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 26 ++++----- .../Drawables/Cards/BeatmapCardThumbnail.cs | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index e3af253db9..d52e8f6c20 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -23,7 +23,6 @@ using osu.Game.Overlays.BeatmapSet; using osuTK; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; -using osuTK.Graphics; using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; namespace osu.Game.Beatmaps.Drawables.Cards @@ -42,7 +41,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapDownloadTracker downloadTracker; - private UpdateableOnlineBeatmapSetCover leftCover; + private BeatmapCardThumbnail thumbnail; private FillFlowContainer leftIconArea; private Container rightAreaBackground; @@ -98,24 +97,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards Colour = Colour4.White }, }, - new Container + thumbnail = new BeatmapCardThumbnail(beatmapSet) { Name = @"Left (icon) area", Size = new Vector2(height), - Children = new Drawable[] + Child = leftIconArea = new FillFlowContainer { - leftCover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) - { - RelativeSizeAxes = Axes.Both, - OnlineInfo = beatmapSet - }, - leftIconArea = new FillFlowContainer - { - Margin = new MarginPadding(5), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(1) - } + Margin = new MarginPadding(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1) } }, new Container @@ -395,10 +386,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards if (IsHovered) targetWidth = targetWidth - icon_area_width + corner_radius; + thumbnail.Dimmed.Value = IsHovered; + mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); mainContentBackground.Dimmed.Value = IsHovered; - leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs new file mode 100644 index 0000000000..b9304e744a --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osuTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardThumbnail : Container + { + public BindableBool Dimmed { get; } = new BindableBool(); + + private readonly APIBeatmapSet beatmapSetInfo; + + private readonly UpdateableOnlineBeatmapSetCover cover; + private readonly Container content; + + protected override Container Content => content; + + public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo) + { + this.beatmapSetInfo = beatmapSetInfo; + + InternalChildren = new Drawable[] + { + cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) + { + RelativeSizeAxes = Axes.Both, + OnlineInfo = beatmapSetInfo + }, + content = new Container + { + RelativeSizeAxes = Axes.Both + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Dimmed.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + cover.FadeColour(Dimmed.Value ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} From b44db9f5e5036fd59cb4e62f2b2a40df56aaff14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 18:05:30 +0200 Subject: [PATCH 02/52] Add test scene for thumbnail component --- .../Beatmaps/TestSceneBeatmapCardThumbnail.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs new file mode 100644 index 0000000000..b7c87219ca --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardThumbnail : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestThumbnailPreview() + { + BeatmapCardThumbnail thumbnail = null; + + AddStep("create thumbnail", () => Child = thumbnail = new BeatmapCardThumbnail(CreateAPIBeatmapSet(Ruleset.Value)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200) + }); + AddToggleStep("toggle dim", dimmed => thumbnail.Dimmed.Value = dimmed); + } + } +} From 5d13686cdf689bd42b7dc9e992cb9bb19fcf8dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 18:26:28 +0200 Subject: [PATCH 03/52] Add play button for card preview --- .../Drawables/Cards/BeatmapCardThumbnail.cs | 3 ++ .../Drawables/Cards/Buttons/PlayButton.cs | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index b9304e744a..75ed5bdcfe 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -17,6 +17,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly APIBeatmapSet beatmapSetInfo; private readonly UpdateableOnlineBeatmapSetCover cover; + private readonly PlayButton playButton; private readonly Container content; protected override Container Content => content; @@ -32,6 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSetInfo }, + playButton = new PlayButton(), content = new Container { RelativeSizeAxes = Axes.Both @@ -48,6 +50,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { + playButton.FadeTo(Dimmed.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); cover.FadeColour(Dimmed.Value ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs new file mode 100644 index 0000000000..c1cbac1c59 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class PlayButton : OsuHoverContainer + { + public PlayButton() + { + Anchor = Origin = Anchor.Centre; + + Child = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Play, + Size = new Vector2(14) + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.Yellow; + } + } +} From 9164f006aa17ac62a17fda40e4260237fc353cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 18:41:31 +0200 Subject: [PATCH 04/52] Implement basic behaviour of play button --- .../Beatmaps/TestSceneBeatmapCardThumbnail.cs | 42 ++++++++++++++++++- .../Drawables/Cards/BeatmapCardThumbnail.cs | 15 +++++-- .../Drawables/Cards/Buttons/PlayButton.cs | 38 ++++++++++++++--- 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index b7c87219ca..73424241cb 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -1,17 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Overlays; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Beatmaps { - public class TestSceneBeatmapCardThumbnail : OsuTestScene + public class TestSceneBeatmapCardThumbnail : OsuManualInputManagerTestScene { + private PlayButton playButton => this.ChildrenOfType().Single(); + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); @@ -26,7 +33,38 @@ namespace osu.Game.Tests.Visual.Beatmaps Origin = Anchor.Centre, Size = new Vector2(200) }); - AddToggleStep("toggle dim", dimmed => thumbnail.Dimmed.Value = dimmed); + AddStep("enable dim", () => thumbnail.Dimmed.Value = true); + AddUntilStep("button visible", () => playButton.IsPresent); + + AddStep("click button", () => + { + InputManager.MoveMouseTo(playButton); + InputManager.Click(MouseButton.Left); + }); + iconIs(FontAwesome.Solid.Stop); + + AddStep("click again", () => + { + InputManager.MoveMouseTo(playButton); + InputManager.Click(MouseButton.Left); + }); + iconIs(FontAwesome.Solid.Play); + + AddStep("click again", () => + { + InputManager.MoveMouseTo(playButton); + InputManager.Click(MouseButton.Left); + }); + iconIs(FontAwesome.Solid.Stop); + + AddStep("disable dim", () => thumbnail.Dimmed.Value = false); + AddWaitStep("wait some", 3); + AddAssert("button still visible", () => playButton.IsPresent); + + AddStep("end track playback", () => playButton.Playing.Value = false); + AddUntilStep("button hidden", () => !playButton.IsPresent); } + + private void iconIs(IconUsage usage) => AddAssert("icon is correct", () => playButton.ChildrenOfType().Single().Icon.Equals(usage)); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 75ed5bdcfe..ce1f856b84 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osuTK.Graphics; @@ -33,7 +34,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSetInfo }, - playButton = new PlayButton(), + playButton = new PlayButton(beatmapSetInfo) + { + RelativeSizeAxes = Axes.Both + }, content = new Container { RelativeSizeAxes = Axes.Both @@ -44,14 +48,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void LoadComplete() { base.LoadComplete(); - Dimmed.BindValueChanged(_ => updateState(), true); + Dimmed.BindValueChanged(_ => updateState()); + playButton.Playing.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - playButton.FadeTo(Dimmed.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - cover.FadeColour(Dimmed.Value ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + bool shouldDim = Dimmed.Value || playButton.Playing.Value; + + playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index c1cbac1c59..c48999ed1a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -12,17 +13,30 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class PlayButton : OsuHoverContainer { - public PlayButton() + public BindableBool Playing { get; } = new BindableBool(); + + private readonly BeatmapSetInfo beatmapSetInfo; + + private readonly SpriteIcon icon; + + public PlayButton(BeatmapSetInfo beatmapSetInfo) { + this.beatmapSetInfo = beatmapSetInfo; + Anchor = Origin = Anchor.Centre; - Child = new SpriteIcon + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Play, - Size = new Vector2(14) + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Play, + Size = new Vector2(14) + }, }; + + Action = () => Playing.Toggle(); } [BackgroundDependencyLoader] @@ -30,5 +44,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { HoverColour = colours.Yellow; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Playing.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + icon.Icon = Playing.Value ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + } } } From 1a1603f0dba556e30a598a1deabe468a3455d810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 19:27:07 +0200 Subject: [PATCH 05/52] Implement preview track playback --- .../Beatmaps/TestSceneBeatmapCardThumbnail.cs | 7 +- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 5 +- .../Drawables/Cards/BeatmapCardThumbnail.cs | 49 ++++++++-- .../Drawables/Cards/Buttons/PlayButton.cs | 92 ++++++++++++++++++- 4 files changed, 135 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index 73424241cb..59df0dc7ca 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Beatmaps InputManager.MoveMouseTo(playButton); InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value); iconIs(FontAwesome.Solid.Stop); AddStep("click again", () => @@ -48,6 +49,7 @@ namespace osu.Game.Tests.Visual.Beatmaps InputManager.MoveMouseTo(playButton); InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for stop", () => !playButton.Playing.Value && playButton.Enabled.Value); iconIs(FontAwesome.Solid.Play); AddStep("click again", () => @@ -55,16 +57,17 @@ namespace osu.Game.Tests.Visual.Beatmaps InputManager.MoveMouseTo(playButton); InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value); iconIs(FontAwesome.Solid.Stop); AddStep("disable dim", () => thumbnail.Dimmed.Value = false); AddWaitStep("wait some", 3); AddAssert("button still visible", () => playButton.IsPresent); - AddStep("end track playback", () => playButton.Playing.Value = false); + AddUntilStep("wait for track to end", () => !playButton.Playing.Value); AddUntilStep("button hidden", () => !playButton.IsPresent); } - private void iconIs(IconUsage usage) => AddAssert("icon is correct", () => playButton.ChildrenOfType().Single().Icon.Equals(usage)); + private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType().Any(icon => icon.Icon.Equals(usage))); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index d52e8f6c20..4ffad0f065 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Name = @"Left (icon) area", Size = new Vector2(height), + Padding = new MarginPadding { Right = corner_radius }, Child = leftIconArea = new FillFlowContainer { Margin = new MarginPadding(5), @@ -310,10 +311,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; if (beatmapSet.HasVideo) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Film)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); if (beatmapSet.HasStoryboard) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Image)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); if (beatmapSet.HasExplicitContent) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index ce1f856b84..f11a5916e1 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Screens.Ranking.Expanded.Accuracy; +using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables.Cards @@ -15,18 +19,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public BindableBool Dimmed { get; } = new BindableBool(); - private readonly APIBeatmapSet beatmapSetInfo; + public new MarginPadding Padding + { + get => foreground.Padding; + set => foreground.Padding = value; + } private readonly UpdateableOnlineBeatmapSetCover cover; + private readonly Container foreground; private readonly PlayButton playButton; + private readonly SmoothCircularProgress progress; private readonly Container content; protected override Container Content => content; public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo) { - this.beatmapSetInfo = beatmapSetInfo; - InternalChildren = new Drawable[] { cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) @@ -34,22 +42,45 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSetInfo }, - playButton = new PlayButton(beatmapSetInfo) + foreground = new Container { - RelativeSizeAxes = Axes.Both - }, - content = new Container - { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + playButton = new PlayButton(beatmapSetInfo) + { + RelativeSizeAxes = Axes.Both + }, + progress = new SmoothCircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(50), + InnerRadius = 0.2f + }, + content = new Container + { + RelativeSizeAxes = Axes.Both + } + } } }; } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + progress.Colour = colourProvider.Highlight1; + } + protected override void LoadComplete() { base.LoadComplete(); Dimmed.BindValueChanged(_ => updateState()); + playButton.Playing.BindValueChanged(_ => updateState(), true); + ((IBindable)progress.Current).BindTo(playButton.Progress); + FinishTransforms(true); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index c48999ed1a..4574d37da0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -1,25 +1,43 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class PlayButton : OsuHoverContainer { + public IBindable Progress => progress; + private readonly BindableDouble progress = new BindableDouble(); + public BindableBool Playing { get; } = new BindableBool(); - private readonly BeatmapSetInfo beatmapSetInfo; + private readonly IBeatmapSetInfo beatmapSetInfo; + + protected override IEnumerable EffectTargets => icon.Yield(); private readonly SpriteIcon icon; + private readonly LoadingSpinner loadingSpinner; - public PlayButton(BeatmapSetInfo beatmapSetInfo) + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } = null!; + + private PreviewTrack? previewTrack; + + public PlayButton(IBeatmapSetInfo beatmapSetInfo) { this.beatmapSetInfo = beatmapSetInfo; @@ -34,6 +52,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Icon = FontAwesome.Solid.Play, Size = new Vector2(14) }, + loadingSpinner = new LoadingSpinner + { + Size = new Vector2(14) + } }; Action = () => Playing.Toggle(); @@ -49,12 +71,72 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { base.LoadComplete(); - Playing.BindValueChanged(_ => updateState(), true); + Playing.BindValueChanged(updateState, true); } - private void updateState() + protected override void Update() { - icon.Icon = Playing.Value ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + base.Update(); + + if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded) + progress.Value = previewTrack.CurrentTime / previewTrack.Length; + else + progress.Value = 0; + } + + private void updateState(ValueChangedEvent playing) + { + icon.Icon = playing.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + + if (!playing.NewValue) + { + stopPreview(); + return; + } + + if (previewTrack == null) + { + toggleLoading(true); + LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded); + } + else + tryStartPreview(); + } + + private void stopPreview() + { + toggleLoading(false); + Playing.Value = false; + previewTrack?.Stop(); + } + + private void onPreviewLoaded(PreviewTrack loadedPreview) + { + // another async load might have completed before this one. + // if so, do not make any changes. + if (loadedPreview != previewTrack) + return; + + AddInternal(loadedPreview); + toggleLoading(false); + + loadedPreview.Stopped += () => Schedule(() => Playing.Value = false); + + if (Playing.Value) + tryStartPreview(); + } + + private void tryStartPreview() + { + if (previewTrack?.Start() == false) + Playing.Value = false; + } + + private void toggleLoading(bool loading) + { + Enabled.Value = !loading; + icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden; } } } From 99a139dc988441f317b87d5bdb4a58db99e4d48d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Nov 2021 13:42:07 +0900 Subject: [PATCH 06/52] Initialise all file lists at construction time (and remove setter) --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 4 +--- osu.Game/Database/ArchiveModelManager.cs | 3 ++- osu.Game/Database/IHasFiles.cs | 4 +++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinInfo.cs | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 35468ce831..1806c2b491 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -34,8 +33,7 @@ namespace osu.Game.Beatmaps public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; - [NotNull] - public List Files { get; set; } = new List(); + public List Files { get; } = new List(); /// /// The maximum star difficulty of all beatmaps in this set. diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index a0e8105285..adbb71c8da 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -392,7 +392,8 @@ namespace osu.Game.Database { LogForModel(item, @"Beginning import..."); - item.Files = archive != null ? createFileInfos(archive, Files) : new List(); + if (archive != null) + item.Files.AddRange(createFileInfos(archive, Files)); item.Hash = ComputeHash(item); await Populate(item, archive, cancellationToken).ConfigureAwait(false); diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs index f6aa941ec2..3f6531832f 100644 --- a/osu.Game/Database/IHasFiles.cs +++ b/osu.Game/Database/IHasFiles.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; namespace osu.Game.Database { @@ -12,7 +13,8 @@ namespace osu.Game.Database public interface IHasFiles where TFile : INamedFileInfo { - List Files { get; set; } + [NotNull] + List Files { get; } string Hash { get; set; } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 26749a23f9..f0c57cb953 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -394,7 +394,7 @@ namespace osu.Game.Online.Leaderboards if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); - if (Score.Files?.Count > 0) + if (Score.Files.Count > 0) items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score))); if (Score.ID != 0) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 736a939a59..b4e194cbed 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -160,7 +160,7 @@ namespace osu.Game.Scoring [NotMapped] public List HitEvents { get; set; } - public List Files { get; set; } + public List Files { get; } = new List(); public string Hash { get; set; } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index fae1a599d1..10526b69af 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -59,7 +59,7 @@ namespace osu.Game.Skinning string filename = $"{skinnableTarget}.json"; // skininfo files may be null for default skin. - var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == filename); + var fileInfo = SkinInfo.Files.FirstOrDefault(f => f.Filename == filename); if (fileInfo == null) continue; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 3b34e23d57..4733e8cdd9 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -36,7 +36,7 @@ namespace osu.Game.Skinning return (Skin)Activator.CreateInstance(type, this, resources); } - public List Files { get; set; } = new List(); + public List Files { get; } = new List(); public bool DeletePending { get; set; } From c2f8d9f677d505e0b82bb76e8c1b589870d8827c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Nov 2021 13:49:38 +0900 Subject: [PATCH 07/52] Update tests which set `Files` lists directly --- .../Editing/Checks/CheckAudioInVideoTest.cs | 5 ++-- .../Checks/CheckBackgroundQualityTest.cs | 8 +++---- .../Editing/Checks/CheckFilePresenceTest.cs | 5 ++-- .../Checks/CheckTooShortAudioFilesTest.cs | 5 ++-- .../Editing/Checks/CheckZeroByteFilesTest.cs | 5 ++-- .../Tests/Beatmaps/HitObjectSampleTest.cs | 24 ++++++++----------- 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs index f3a4f10210..f9b16990ef 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.IO; using System.Linq; using Moq; @@ -33,14 +32,14 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { new BeatmapSetFileInfo { Filename = "abc123.mp4", FileInfo = new FileInfo { Hash = "abcdef" } } - }) + } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 05bfae7e63..5a45857e5c 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System; using System.IO; using System.Linq; using JetBrains.Annotations; @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Editing.Checks Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { new BeatmapSetFileInfo { @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Editing.Checks Hash = "abcdef" } } - }) + } } } }; @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Editing.Checks { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = string.Empty; - var context = getContext(null, new MemoryStream(System.Array.Empty())); + var context = getContext(null, new MemoryStream(Array.Empty())); Assert.That(check.Run(context), Is.Empty); } diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 70e4c76b19..59dfc88923 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; using osu.Game.Beatmaps; @@ -30,14 +29,14 @@ namespace osu.Game.Tests.Editing.Checks Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { new BeatmapSetFileInfo { Filename = "abc123.jpg", FileInfo = new FileInfo { Hash = "abcdef" } } - }) + } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 9b090591bc..64285cd46e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.IO; using System.Linq; using ManagedBass; @@ -34,14 +33,14 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { new BeatmapSetFileInfo { Filename = "abc123.wav", FileInfo = new FileInfo { Hash = "abcdef" } } - }) + } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs index c9adc030c1..1a581cc27d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.IO; using System.Linq; using Moq; @@ -30,14 +29,14 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = new List(new[] + Files = { new BeatmapSetFileInfo { Filename = "abc123.jpg", FileInfo = new FileInfo { Hash = "abcdef" } } - }) + } } } }; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index b87c3d57c2..127c853a8e 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -91,23 +91,19 @@ namespace osu.Game.Tests.Beatmaps { AddStep("setup skins", () => { - userSkinInfo.Files = new List + userSkinInfo.Files.Clear(); + userSkinInfo.Files.Add(new SkinFileInfo { - new SkinFileInfo - { - Filename = userFile, - FileInfo = new IO.FileInfo { Hash = userFile } - } - }; + Filename = userFile, + FileInfo = new IO.FileInfo { Hash = userFile } + }); - beatmapInfo.BeatmapSet.Files = new List + beatmapInfo.BeatmapSet.Files.Clear(); + beatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo { - new BeatmapSetFileInfo - { - Filename = beatmapFile, - FileInfo = new IO.FileInfo { Hash = beatmapFile } - } - }; + Filename = beatmapFile, + FileInfo = new IO.FileInfo { Hash = beatmapFile } + }); // Need to refresh the cached skin source to refresh the skin resource store. dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this)); From c7bc03e6f70ebe69de6906def31e7ccad4459ba9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Nov 2021 14:22:41 +0900 Subject: [PATCH 08/52] Add helper method for editor check tests --- .../Editing/Checks/CheckAudioInVideoTest.cs | 7 +------ .../Checks/CheckBackgroundQualityTest.cs | 17 ++++------------- .../Editing/Checks/CheckFilePresenceTest.cs | 14 ++++---------- .../Editing/Checks/CheckTestHelpers.cs | 18 ++++++++++++++++++ .../Checks/CheckTooShortAudioFilesTest.cs | 16 ++-------------- .../Editing/Checks/CheckZeroByteFilesTest.cs | 7 +------ 6 files changed, 30 insertions(+), 49 deletions(-) create mode 100644 osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs index f9b16990ef..f9b7bfa586 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -34,11 +33,7 @@ namespace osu.Game.Tests.Editing.Checks { Files = { - new BeatmapSetFileInfo - { - Filename = "abc123.mp4", - FileInfo = new FileInfo { Hash = "abcdef" } - } + CheckTestHelpers.CreateMockFile("mp4"), } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 5a45857e5c..bb560054a3 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -12,7 +12,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -25,25 +24,17 @@ namespace osu.Game.Tests.Editing.Checks [SetUp] public void Setup() { + var file = CheckTestHelpers.CreateMockFile("jpg"); + check = new CheckBackgroundQuality(); beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { - Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + Metadata = new BeatmapMetadata { BackgroundFile = file.Filename }, BeatmapSet = new BeatmapSetInfo { - Files = - { - new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo - { - Hash = "abcdef" - } - } - } + Files = { file } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 59dfc88923..f36454aa71 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.IO; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -21,22 +20,17 @@ namespace osu.Game.Tests.Editing.Checks [SetUp] public void Setup() { + var file = CheckTestHelpers.CreateMockFile("jpg"); + check = new CheckBackgroundPresence(); beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { - Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + Metadata = new BeatmapMetadata { BackgroundFile = file.Filename }, BeatmapSet = new BeatmapSetInfo { - Files = - { - new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo { Hash = "abcdef" } - } - } + Files = { file } } } }; diff --git a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs new file mode 100644 index 0000000000..f702921986 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.IO; + +namespace osu.Game.Tests.Editing.Checks +{ + public static class CheckTestHelpers + { + public static BeatmapSetFileInfo CreateMockFile(string extension) => + new BeatmapSetFileInfo + { + Filename = $"abc123.{extension}", + FileInfo = new FileInfo { Hash = "abcdef" } + }; + } +} diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 64285cd46e..8adf0d3764 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK.Audio; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -33,14 +32,7 @@ namespace osu.Game.Tests.Editing.Checks { BeatmapSet = new BeatmapSetInfo { - Files = - { - new BeatmapSetFileInfo - { - Filename = "abc123.wav", - FileInfo = new FileInfo { Hash = "abcdef" } - } - } + Files = { CheckTestHelpers.CreateMockFile("wav") } } } }; @@ -54,11 +46,7 @@ namespace osu.Game.Tests.Editing.Checks public void TestDifferentExtension() { beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); - beatmap.BeatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo { Hash = "abcdef" } - }); + beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg")); // Should fail to load, but not produce an error due to the extension not being expected to load. Assert.IsEmpty(check.Run(getContext(null, allowMissing: true))); diff --git a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs index 1a581cc27d..79d00e6a60 100644 --- a/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Tests.Editing.Checks { @@ -31,11 +30,7 @@ namespace osu.Game.Tests.Editing.Checks { Files = { - new BeatmapSetFileInfo - { - Filename = "abc123.jpg", - FileInfo = new FileInfo { Hash = "abcdef" } - } + CheckTestHelpers.CreateMockFile("jpg"), } } } From 132bb592030667a967425447714aa2f074d7666e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 19:53:21 +0900 Subject: [PATCH 09/52] Update working beatmap when returning to match --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 7 +++++++ osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + 2 files changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index dcb01b83cc..aaf0a49c96 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -78,6 +78,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); } + [Test] + public void TestCorrectItemSelectedAfterNewItemAdded() + { + addItem(() => OtherBeatmap); + AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + } + private void addItem(Func beatmap) { AddStep("click edit button", () => diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 323d38c881..7c5ed3f5cc 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -302,6 +302,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnResuming(IScreen last) { base.OnResuming(last); + updateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); } From 016684b52d65db6e1e94723f134a4d03dc16549a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 20:29:47 +0900 Subject: [PATCH 10/52] Remove unreachable code --- osu.Game/Skinning/LegacySkinResourceStore.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 096b467867..c4418baeff 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -24,9 +24,6 @@ namespace osu.Game.Skinning protected override IEnumerable GetFilenames(string name) { - if (source.Files == null) - yield break; - foreach (string filename in base.GetFilenames(name)) { string path = getPathForFile(filename.ToStandardisedPath()); From 1907b42f8252900ff0d3d83fa34f0eb1d5d9f6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Nov 2021 20:34:33 +0100 Subject: [PATCH 11/52] Use constant online IDs in tests to avoid preview not working randomly --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 13 ++++++++++--- .../Beatmaps/TestSceneBeatmapCardThumbnail.cs | 14 ++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index addec15881..a45ba30ccd 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -23,6 +23,11 @@ namespace osu.Game.Tests.Visual.Beatmaps { public class TestSceneBeatmapCard : OsuTestScene { + /// + /// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources. + /// + private const int online_id = 163112; + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private APIBeatmapSet[] testCases; @@ -38,7 +43,6 @@ namespace osu.Game.Tests.Visual.Beatmaps var normal = CreateAPIBeatmapSet(Ruleset.Value); normal.HasVideo = true; normal.HasStoryboard = true; - normal.OnlineID = 241526; var withStatistics = CreateAPIBeatmapSet(Ruleset.Value); withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats"; @@ -106,6 +110,9 @@ namespace osu.Game.Tests.Visual.Beatmaps explicitFeaturedMap, longName }; + + foreach (var testCase in testCases) + testCase.OnlineID = online_id; } private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet @@ -191,9 +198,9 @@ namespace osu.Game.Tests.Visual.Beatmaps private void ensureSoleilyRemoved() { AddUntilStep("ensure manager loaded", () => beatmaps != null); - AddStep("remove soleily", () => + AddStep("remove map", () => { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == online_id); if (beatmap != null) beatmaps.Delete(beatmap); }); diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index 59df0dc7ca..1b4542d946 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -27,11 +27,17 @@ namespace osu.Game.Tests.Visual.Beatmaps { BeatmapCardThumbnail thumbnail = null; - AddStep("create thumbnail", () => Child = thumbnail = new BeatmapCardThumbnail(CreateAPIBeatmapSet(Ruleset.Value)) + AddStep("create thumbnail", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200) + var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online. + + Child = thumbnail = new BeatmapCardThumbnail(beatmapSet) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200) + }; }); AddStep("enable dim", () => thumbnail.Dimmed.Value = true); AddUntilStep("button visible", () => playButton.IsPresent); From 306e13fa7b11ab242300d3a4a4c314dd014d098a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 14:06:02 +0900 Subject: [PATCH 12/52] Remove all usages of `FileStore.QueryFiles` --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- osu.Game/IO/FileStore.cs | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cac1bf05a6..6e2b9d20a8 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1050,7 +1050,7 @@ namespace osu.Game.Tests.Beatmaps.IO private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) { - Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); + Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1)); } private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 165cf756aa..ebe1ebfe69 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -31,13 +28,6 @@ namespace osu.Game.IO Store = new StorageBackedResourceStore(Storage); } - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public IEnumerable QueryFiles(Expression> query) => ContextFactory.Get().Set().AsNoTracking().Where(f => f.ReferenceCount > 0).Where(query); - public FileInfo Add(Stream data, bool reference = true) { using (var usage = ContextFactory.GetForWrite()) From fd321109da8f9f621c7ae5b0e3dfb974154a1fc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 14:06:12 +0900 Subject: [PATCH 13/52] Remove unnecessary `virtual` specification on `Refresh` --- osu.Game/Database/DatabaseBackedStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index a11efba54b..03e1c014b2 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -19,7 +19,7 @@ namespace osu.Game.Database /// The object to use as a reference when negotiating a local instance. /// An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes. /// A valid EF-stored type. - protected virtual void Refresh(ref T obj, IQueryable lookupSource = null) where T : class, IHasPrimaryKey + protected void Refresh(ref T obj, IQueryable lookupSource = null) where T : class, IHasPrimaryKey { using (var usage = ContextFactory.GetForWrite()) { From 204bd2b604a30f13c899d14a804f1a1582b1463e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 14:17:42 +0900 Subject: [PATCH 14/52] Ensure `Cleanup` can run from non-update thread and add basic log output --- osu.Game/Stores/RealmFileStore.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index aebf330443..f9abbda4c0 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -86,9 +86,13 @@ namespace osu.Game.Stores public void Cleanup() { - var realm = realmFactory.Context; + Logger.Log(@"Beginning realm file store cleanup"); + + int totalFiles = 0; + int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. + using (var realm = realmFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) @@ -96,11 +100,14 @@ namespace osu.Game.Stores foreach (var file in files) { + totalFiles++; + if (file.BacklinksCount > 0) continue; try { + removedFiles++; Storage.Delete(file.GetStoragePath()); realm.Remove(file); } @@ -112,6 +119,8 @@ namespace osu.Game.Stores transaction.Commit(); } + + Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)"); } } } From 315e126497db5d6c343b57662d5e66ea4fefa834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 14:12:49 +0900 Subject: [PATCH 15/52] Add automatic clean-up call for `RealmFileStore` --- osu.Game/Database/RealmContextFactory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 738ecaea7c..0cffb2b0ae 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -14,6 +14,7 @@ using osu.Framework.Statistics; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Models; +using osu.Game.Stores; using Realms; #nullable enable @@ -106,6 +107,8 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { + new RealmFileStore(this, storage).Cleanup(); + using (var realm = CreateContext()) using (var transaction = realm.BeginWrite()) { From bcdb73e1e847cc2a2d099f4c24600fbae3b5d228 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 14:28:27 +0900 Subject: [PATCH 16/52] Clean up files last --- osu.Game/Database/RealmContextFactory.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0cffb2b0ae..3c5dfeafe8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -107,8 +107,6 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { - new RealmFileStore(this, storage).Cleanup(); - using (var realm = CreateContext()) using (var transaction = realm.BeginWrite()) { @@ -124,6 +122,10 @@ namespace osu.Game.Database transaction.Commit(); } + + // clean up files after dropping any pending deletions. + // in the future we may want to only do this when the game is idle, rather than on every startup. + new RealmFileStore(this, storage).Cleanup(); } /// From 60b207f20aa34f31aefd723716a347d6248aa574 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 14:34:13 +0900 Subject: [PATCH 17/52] Reduce interface exposure of `ScoreManager` --- osu.Game/Scoring/ScoreManager.cs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 29144e7bdc..abc0bfdda7 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring { - public class ScoreManager : IModelManager, IModelImporter, IModelFileManager, IModelDownloader + public class ScoreManager : IModelManager, IModelImporter, IModelDownloader { private readonly Scheduler scheduler; private readonly Func difficulties; @@ -342,25 +342,6 @@ namespace osu.Game.Scoring #endregion - #region Implementation of IModelFileManager - - public void ReplaceFile(ScoreInfo model, ScoreFileInfo file, Stream contents, string filename = null) - { - scoreModelManager.ReplaceFile(model, file, contents, filename); - } - - public void DeleteFile(ScoreInfo model, ScoreFileInfo file) - { - scoreModelManager.DeleteFile(model, file); - } - - public void AddFile(ScoreInfo model, Stream contents, string filename) - { - scoreModelManager.AddFile(model, contents, filename); - } - - #endregion - #region Implementation of IModelDownloader public event Action> DownloadBegan From 2bfc47368952dce7ef01f914dc06551be784a445 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 15:14:43 +0900 Subject: [PATCH 18/52] Split out `SkinModelManager` from `SkinManager` --- osu.Game/Skinning/SkinManager.cs | 295 +++++++++++--------------- osu.Game/Skinning/SkinModelManager.cs | 191 +++++++++++++++++ 2 files changed, 317 insertions(+), 169 deletions(-) create mode 100644 osu.Game/Skinning/SkinModelManager.cs diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 6e0a8e0a11..a840e17ed1 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -18,15 +18,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Overlays.Notifications; namespace osu.Game.Skinning { @@ -38,7 +37,7 @@ namespace osu.Game.Skinning /// For gameplay components, see which adds extra legacy and toggle logic that may affect the lookup process. /// [ExcludeFromDynamicCompile] - public class SkinManager : ArchiveModelManager, ISkinSource, IStorageResourceProvider + public class SkinManager : ISkinSource, IStorageResourceProvider, IModelImporter, IModelManager { private readonly AudioManager audio; @@ -49,11 +48,11 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; - public override IEnumerable HandledExtensions => new[] { ".osk" }; + private readonly SkinModelManager skinModelManager; - protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; + private readonly SkinStore skinStore; - protected override string ImportFromStablePath => "Skins"; + private readonly IResourceStore userFiles; /// /// The default skin. @@ -66,12 +65,16 @@ namespace osu.Game.Skinning public Skin DefaultLegacySkin { get; } public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) - : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) { this.audio = audio; this.host = host; this.resources = resources; + skinStore = new SkinStore(contextFactory, storage); + userFiles = new FileStore(contextFactory, storage).Store; + + skinModelManager = new SkinModelManager(storage, contextFactory, skinStore, host, this); + DefaultLegacySkin = new DefaultLegacySkin(this); DefaultSkin = new DefaultSkin(this); @@ -85,31 +88,8 @@ namespace osu.Game.Skinning SourceChanged?.Invoke(); }; - - // can be removed 20220420. - populateMissingHashes(); } - private void populateMissingHashes() - { - var skinsWithoutHashes = ModelStore.ConsumableItems.Where(i => i.Hash == null).ToArray(); - - foreach (SkinInfo skin in skinsWithoutHashes) - { - try - { - Update(skin); - } - catch (Exception e) - { - Delete(skin); - Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid"); - } - } - } - - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk"; - /// /// Returns a list of all usable s. Includes the special default skin plus all skins from . /// @@ -129,15 +109,15 @@ namespace osu.Game.Skinning public List GetAllUserSkins(bool includeFiles = false) { if (includeFiles) - return ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); + return skinStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); - return ModelStore.Items.Where(s => !s.DeletePending).ToList(); + return skinStore.Items.Where(s => !s.DeletePending).ToList(); } public void SelectRandomSkin() { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = ModelStore.Items.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = skinStore.Items.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { @@ -146,137 +126,7 @@ namespace osu.Game.Skinning } var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); - CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID); - } - - protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" }; - - private const string unknown_creator_string = @"Unknown"; - - protected override bool HasCustomHashFunction => true; - - protected override string ComputeHash(SkinInfo item) - { - var instance = GetSkin(item); - - // This function can be run on fresh import or save. The logic here ensures a skin.ini file is in a good state for both operations. - - // `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above. - string skinIniSourcedName = instance.Configuration.SkinInfo.Name; - string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator; - string archiveName = item.Name.Replace(@".osk", string.Empty, StringComparison.OrdinalIgnoreCase); - - bool isImport = item.ID == 0; - - if (isImport) - { - item.Name = !string.IsNullOrEmpty(skinIniSourcedName) ? skinIniSourcedName : archiveName; - item.Creator = !string.IsNullOrEmpty(skinIniSourcedCreator) ? skinIniSourcedCreator : unknown_creator_string; - - // For imports, we want to use the archive or folder name as part of the metadata, in addition to any existing skin.ini metadata. - // In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications. - // In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin. - if (archiveName != item.Name) - item.Name = @$"{item.Name} [{archiveName}]"; - } - - // By this point, the metadata in SkinInfo will be correct. - // Regardless of whether this is an import or not, let's write the skin.ini if non-existing or non-matching. - // This is (weirdly) done inside ComputeHash to avoid adding a new method to handle this case. After switching to realm it can be moved into another place. - if (skinIniSourcedName != item.Name) - updateSkinIniMetadata(item); - - return base.ComputeHash(item); - } - - private void updateSkinIniMetadata(SkinInfo item) - { - string nameLine = @$"Name: {item.Name}"; - string authorLine = @$"Author: {item.Creator}"; - - string[] newLines = - { - @"// The following content was automatically added by osu! during import, based on filename / folder metadata.", - @"[General]", - nameLine, - authorLine, - }; - - var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)); - - if (existingFile == null) - { - // In the case a skin doesn't have a skin.ini yet, let's create one. - writeNewSkinIni(); - return; - } - - using (Stream stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.GetStoragePath())) - using (var sr = new StreamReader(existingStream)) - { - string line; - while ((line = sr.ReadLine()) != null) - sw.WriteLine(line); - } - - sw.WriteLine(); - - foreach (string line in newLines) - sw.WriteLine(line); - } - - ReplaceFile(item, existingFile, stream); - - // can be removed 20220502. - if (!ensureIniWasUpdated(item)) - { - Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important); - - DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))); - writeNewSkinIni(); - } - } - - void writeNewSkinIni() - { - using (Stream stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - foreach (string line in newLines) - sw.WriteLine(line); - } - - AddFile(item, stream, @"skin.ini"); - } - } - } - - private bool ensureIniWasUpdated(SkinInfo item) - { - // This is a final consistency check to ensure that hash computation doesn't enter an infinite loop. - // With other changes to the surrounding code this should never be hit, but until we are 101% sure that there - // are no other cases let's avoid a hard startup crash by bailing and alerting. - - var instance = GetSkin(item); - - return instance.Configuration.SkinInfo.Name == item.Name; - } - - protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) - { - var instance = GetSkin(model); - - model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); - - model.Name = instance.Configuration.SkinInfo.Name; - model.Creator = instance.Configuration.SkinInfo.Creator; - - return Task.CompletedTask; + CurrentSkinInfo.Value = skinStore.ConsumableItems.Single(i => i.ID == chosen.ID); } /// @@ -297,7 +147,7 @@ namespace osu.Game.Skinning var skin = CurrentSkin.Value; // if the user is attempting to save one of the default skin implementations, create a copy first. - CurrentSkinInfo.Value = Import(new SkinInfo + CurrentSkinInfo.Value = skinModelManager.Import(new SkinInfo { Name = skin.SkinInfo.Name + @" (modified)", Creator = skin.SkinInfo.Creator, @@ -321,9 +171,9 @@ namespace osu.Game.Skinning var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename); if (oldFile != null) - ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename); + skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename); else - AddFile(skin.SkinInfo, streamContent, filename); + skinModelManager.AddFile(skin.SkinInfo, streamContent, filename); } } } @@ -333,7 +183,7 @@ namespace osu.Game.Skinning /// /// The query. /// The first result for the provided query, or null if no results were found. - public SkinInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); + public SkinInfo Query(Expression> query) => skinStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); public event Action SourceChanged; @@ -386,9 +236,116 @@ namespace osu.Game.Skinning AudioManager IStorageResourceProvider.AudioManager => audio; IResourceStore IStorageResourceProvider.Resources => resources; - IResourceStore IStorageResourceProvider.Files => Files.Store; + IResourceStore IStorageResourceProvider.Files => userFiles; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); #endregion + + #region Implementation of IModelImporter + + public Action PostNotification + { + set => skinModelManager.PostNotification = value; + } + + public Action>> PostImport + { + set => skinModelManager.PostImport = value; + } + + public Task Import(params string[] paths) + { + return skinModelManager.Import(paths); + } + + public Task Import(params ImportTask[] tasks) + { + return skinModelManager.Import(tasks); + } + + public IEnumerable HandledExtensions => skinModelManager.HandledExtensions; + + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + { + return skinModelManager.Import(notification, tasks); + } + + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + { + return skinModelManager.Import(task, lowPriority, cancellationToken); + } + + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + { + return skinModelManager.Import(archive, lowPriority, cancellationToken); + } + + public Task> Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + { + return skinModelManager.Import(item, archive, lowPriority, cancellationToken); + } + + #endregion + + #region Implementation of IModelManager + + public event Action ItemUpdated + { + add => skinModelManager.ItemUpdated += value; + remove => skinModelManager.ItemUpdated -= value; + } + + public event Action ItemRemoved + { + add => 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) + { + skinModelManager.Update(item); + } + + public bool Delete(SkinInfo item) + { + return skinModelManager.Delete(item); + } + + public void Delete(List items, bool silent = false) + { + skinModelManager.Delete(items, silent); + } + + public void Undelete(List items, bool silent = false) + { + skinModelManager.Undelete(items, silent); + } + + public void Undelete(SkinInfo item) + { + skinModelManager.Undelete(item); + } + + public bool IsAvailableLocally(SkinInfo model) + { + return skinModelManager.IsAvailableLocally(model); + } + + #endregion } } diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs new file mode 100644 index 0000000000..f28b0c066b --- /dev/null +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -0,0 +1,191 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.Extensions; +using osu.Game.IO; +using osu.Game.IO.Archives; + +namespace osu.Game.Skinning +{ + public class SkinModelManager : ArchiveModelManager + { + private readonly IStorageResourceProvider skinResources; + + public SkinModelManager(Storage storage, DatabaseContextFactory contextFactory, SkinStore skinStore, GameHost host, IStorageResourceProvider skinResources) + : base(storage, contextFactory, skinStore, host) + { + this.skinResources = skinResources; + + // can be removed 20220420. + populateMissingHashes(); + } + + public override IEnumerable HandledExtensions => new[] { ".osk" }; + + 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 SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" }; + + private const string unknown_creator_string = @"Unknown"; + + protected override bool HasCustomHashFunction => true; + + protected override string ComputeHash(SkinInfo item) + { + var instance = createInstance(item); + + // This function can be run on fresh import or save. The logic here ensures a skin.ini file is in a good state for both operations. + + // `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above. + string skinIniSourcedName = instance.Configuration.SkinInfo.Name; + string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator; + string archiveName = item.Name.Replace(@".osk", string.Empty, StringComparison.OrdinalIgnoreCase); + + bool isImport = item.ID == 0; + + if (isImport) + { + item.Name = !string.IsNullOrEmpty(skinIniSourcedName) ? skinIniSourcedName : archiveName; + item.Creator = !string.IsNullOrEmpty(skinIniSourcedCreator) ? skinIniSourcedCreator : unknown_creator_string; + + // For imports, we want to use the archive or folder name as part of the metadata, in addition to any existing skin.ini metadata. + // In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications. + // In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin. + if (archiveName != item.Name) + item.Name = @$"{item.Name} [{archiveName}]"; + } + + // By this point, the metadata in SkinInfo will be correct. + // Regardless of whether this is an import or not, let's write the skin.ini if non-existing or non-matching. + // This is (weirdly) done inside ComputeHash to avoid adding a new method to handle this case. After switching to realm it can be moved into another place. + if (skinIniSourcedName != item.Name) + updateSkinIniMetadata(item); + + return base.ComputeHash(item); + } + + private void updateSkinIniMetadata(SkinInfo item) + { + string nameLine = @$"Name: {item.Name}"; + string authorLine = @$"Author: {item.Creator}"; + + string[] newLines = + { + @"// The following content was automatically added by osu! during import, based on filename / folder metadata.", + @"[General]", + nameLine, + authorLine, + }; + + var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)); + + if (existingFile == null) + { + // In the case a skin doesn't have a skin.ini yet, let's create one. + writeNewSkinIni(); + return; + } + + using (Stream stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + { + using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.GetStoragePath())) + using (var sr = new StreamReader(existingStream)) + { + string line; + while ((line = sr.ReadLine()) != null) + sw.WriteLine(line); + } + + sw.WriteLine(); + + foreach (string line in newLines) + sw.WriteLine(line); + } + + ReplaceFile(item, existingFile, stream); + + // can be removed 20220502. + if (!ensureIniWasUpdated(item)) + { + Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important); + + DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))); + writeNewSkinIni(); + } + } + + void writeNewSkinIni() + { + using (Stream stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + { + foreach (string line in newLines) + sw.WriteLine(line); + } + + AddFile(item, stream, @"skin.ini"); + } + } + } + + private bool ensureIniWasUpdated(SkinInfo item) + { + // This is a final consistency check to ensure that hash computation doesn't enter an infinite loop. + // With other changes to the surrounding code this should never be hit, but until we are 101% sure that there + // are no other cases let's avoid a hard startup crash by bailing and alerting. + + var instance = createInstance(item); + + return instance.Configuration.SkinInfo.Name == item.Name; + } + + protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + { + var instance = createInstance(model); + + model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); + + model.Name = instance.Configuration.SkinInfo.Name; + model.Creator = instance.Configuration.SkinInfo.Creator; + + return Task.CompletedTask; + } + + private void populateMissingHashes() + { + var skinsWithoutHashes = ModelStore.ConsumableItems.Where(i => i.Hash == null).ToArray(); + + foreach (SkinInfo skin in skinsWithoutHashes) + { + try + { + Update(skin); + } + catch (Exception e) + { + Delete(skin); + Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid"); + } + } + } + + private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources); + } +} From 6cab7b877dc40883e860ad4e7f10c7e8c5fa68e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 15:36:12 +0900 Subject: [PATCH 19/52] Move stable import handling into its own class --- osu.Game/Beatmaps/BeatmapManager.cs | 5 -- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 -- osu.Game/Database/ArchiveModelManager.cs | 34 ------------ osu.Game/Database/IModelManager.cs | 7 --- osu.Game/Database/StableBeatmapImporter.cs | 18 +++++++ osu.Game/Database/StableImportManager.cs | 10 ++-- osu.Game/Database/StableImporter.cs | 60 ++++++++++++++++++++++ osu.Game/Database/StableScoreImporter.cs | 23 +++++++++ osu.Game/Database/StableSkinImporter.cs | 14 +++++ osu.Game/Scoring/ScoreManager.cs | 6 --- osu.Game/Scoring/ScoreModelManager.cs | 6 --- osu.Game/Skinning/SkinManager.cs | 5 -- osu.Game/Skinning/SkinModelManager.cs | 2 - 13 files changed, 122 insertions(+), 72 deletions(-) create mode 100644 osu.Game/Database/StableBeatmapImporter.cs create mode 100644 osu.Game/Database/StableImporter.cs create mode 100644 osu.Game/Database/StableScoreImporter.cs create mode 100644 osu.Game/Database/StableSkinImporter.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0594cd1316..2cca3ceaeb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -225,11 +225,6 @@ namespace osu.Game.Beatmaps remove => beatmapModelManager.ItemRemoved -= value; } - public Task ImportFromStableAsync(StableStorage stableStorage) - { - return beatmapModelManager.ImportFromStableAsync(stableStorage); - } - public void Export(BeatmapSetInfo item) { beatmapModelManager.Export(item); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ae395c6da6..63cdd0b852 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -58,10 +58,6 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - protected override string ImportFromStablePath => "."; - - protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage(); - private readonly BeatmapStore beatmaps; private readonly RulesetStore rulesets; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index adbb71c8da..e5919cb5c4 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -728,17 +728,6 @@ namespace osu.Game.Database #region osu-stable import - /// - /// The relative path from osu-stable's data directory to import items from. - /// - protected virtual string ImportFromStablePath => null; - - /// - /// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in . - /// - protected virtual IEnumerable GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath) - .Select(path => storage.GetFullPath(path)); - /// /// Whether this specified path should be removed after successful import. /// @@ -746,29 +735,6 @@ namespace osu.Game.Database /// Whether to perform deletion. 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)); - } - - /// - /// Run any required traversal operations on the stable storage location before performing operations. - /// - /// The stable storage. - /// The usable storage. Return the unchanged if no traversal is required. - protected virtual Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage; - #endregion /// diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 15ad455f21..6c9cca7c7a 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; -using osu.Game.IO; namespace osu.Game.Database { @@ -26,11 +24,6 @@ namespace osu.Game.Database /// event Action ItemRemoved; - /// - /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. - /// - Task ImportFromStableAsync(StableStorage stableStorage); - /// /// Exports an item to a legacy (.zip based) package. /// diff --git a/osu.Game/Database/StableBeatmapImporter.cs b/osu.Game/Database/StableBeatmapImporter.cs new file mode 100644 index 0000000000..7aaa8ba013 --- /dev/null +++ b/osu.Game/Database/StableBeatmapImporter.cs @@ -0,0 +1,18 @@ +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.IO; + +namespace osu.Game.Database +{ + public class StableBeatmapImporter : StableImporter + { + protected override string ImportFromStablePath => "."; + + protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage(); + + public StableBeatmapImporter(IModelImporter importer) + : base(importer) + { + } + } +} \ No newline at end of file diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index fe8c14c085..0a30fcb2be 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -51,18 +51,22 @@ namespace osu.Game.Database var stableStorage = await getStableStorage().ConfigureAwait(false); var importTasks = new List(); + var beatmapImporter = new StableBeatmapImporter(beatmaps); + var skinImporter = new StableSkinImporter(skins); + var scoreImporter = new StableScoreImporter(scores); + Task beatmapImportTask = Task.CompletedTask; if (content.HasFlagFast(StableContent.Beatmaps)) - importTasks.Add(beatmapImportTask = beatmaps.ImportFromStableAsync(stableStorage)); + importTasks.Add(beatmapImportTask = beatmapImporter.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Skins)) - importTasks.Add(skins.ImportFromStableAsync(stableStorage)); + importTasks.Add(skinImporter.ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => scoreImporter.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); } diff --git a/osu.Game/Database/StableImporter.cs b/osu.Game/Database/StableImporter.cs new file mode 100644 index 0000000000..e56737959e --- /dev/null +++ b/osu.Game/Database/StableImporter.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . 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 +{ + /// + /// A class which handled importing various user data from osu-stable. + /// + public class StableImporter + where TModel : class + { + /// + /// The relative path from osu-stable's data directory to import items from. + /// + protected virtual string ImportFromStablePath => null; + + /// + /// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in . + /// + protected virtual IEnumerable GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath) + .Select(path => storage.GetFullPath(path)); + + protected readonly IModelImporter Importer; + + public StableImporter(IModelImporter 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)); + } + + /// + /// Run any required traversal operations on the stable storage location before performing operations. + /// + /// The stable storage. + /// The usable storage. Return the unchanged if no traversal is required. + protected virtual Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage; + } +} diff --git a/osu.Game/Database/StableScoreImporter.cs b/osu.Game/Database/StableScoreImporter.cs new file mode 100644 index 0000000000..fede10c7ea --- /dev/null +++ b/osu.Game/Database/StableScoreImporter.cs @@ -0,0 +1,23 @@ +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 StableScoreImporter : StableImporter + { + protected override string ImportFromStablePath => Path.Combine("Data", "r"); + + protected override IEnumerable 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 StableScoreImporter(IModelImporter importer) + : base(importer) + { + } + } +} diff --git a/osu.Game/Database/StableSkinImporter.cs b/osu.Game/Database/StableSkinImporter.cs new file mode 100644 index 0000000000..65601e85b7 --- /dev/null +++ b/osu.Game/Database/StableSkinImporter.cs @@ -0,0 +1,14 @@ +using osu.Game.Skinning; + +namespace osu.Game.Database +{ + public class StableSkinImporter : StableImporter + { + protected override string ImportFromStablePath => "Skins"; + + public StableSkinImporter(IModelImporter importer) + : base(importer) + { + } + } +} \ No newline at end of file diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 29144e7bdc..d25671d77e 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -15,7 +15,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays.Notifications; @@ -263,11 +262,6 @@ namespace osu.Game.Scoring remove => scoreModelManager.ItemRemoved -= value; } - public Task ImportFromStableAsync(StableStorage stableStorage) - { - return scoreModelManager.ImportFromStableAsync(stableStorage); - } - public void Export(ScoreInfo item) { scoreModelManager.Export(item); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index c194a7166d..9da739237b 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -26,8 +26,6 @@ namespace osu.Game.Scoring protected override string[] HashableFileTypes => new[] { ".osr" }; - protected override string ImportFromStablePath => Path.Combine("Data", "r"); - private readonly RulesetStore rulesets; private readonly Func beatmaps; @@ -81,9 +79,5 @@ namespace osu.Game.Scoring using (var inputStream = Files.Storage.GetStream(file.FileInfo.GetStoragePath())) inputStream.CopyTo(outputStream); } - - protected override IEnumerable GetStableImportPaths(Storage storage) - => storage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) - .Select(path => storage.GetFullPath(path)); } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index a840e17ed1..679b35799e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -301,11 +301,6 @@ namespace osu.Game.Skinning remove => skinModelManager.ItemRemoved -= value; } - public Task ImportFromStableAsync(StableStorage stableStorage) - { - return skinModelManager.ImportFromStableAsync(stableStorage); - } - public void Export(SkinInfo item) { skinModelManager.Export(item); diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index f28b0c066b..572ae5cbfc 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -34,8 +34,6 @@ namespace osu.Game.Skinning 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 SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" }; From 9dcb20a8217ff9dfe62487c8d578a7e42ab8ab35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 15:39:05 +0900 Subject: [PATCH 20/52] Rename `Stable` to `Legacy` and add xmldoc --- ...ImportManager.cs => LeagcyImportManager.cs} | 11 +++++++---- ...mapImporter.cs => LegacyBeatmapImporter.cs} | 6 +++--- .../{StableImporter.cs => LegacyImporter.cs} | 6 +++--- ...ScoreImporter.cs => LegacyScoreImporter.cs} | 4 ++-- ...leSkinImporter.cs => LegacySkinImporter.cs} | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- .../Sections/Maintenance/GeneralSettings.cs | 18 +++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 8 files changed, 31 insertions(+), 28 deletions(-) rename osu.Game/Database/{StableImportManager.cs => LeagcyImportManager.cs} (90%) rename osu.Game/Database/{StableBeatmapImporter.cs => LegacyBeatmapImporter.cs} (74%) rename osu.Game/Database/{StableImporter.cs => LegacyImporter.cs} (92%) rename osu.Game/Database/{StableScoreImporter.cs => LegacyScoreImporter.cs} (83%) rename osu.Game/Database/{StableSkinImporter.cs => LegacySkinImporter.cs} (60%) diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/LeagcyImportManager.cs similarity index 90% rename from osu.Game/Database/StableImportManager.cs rename to osu.Game/Database/LeagcyImportManager.cs index 0a30fcb2be..9f3f9d007a 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/LeagcyImportManager.cs @@ -19,7 +19,10 @@ using osu.Game.Skinning; namespace osu.Game.Database { - public class StableImportManager : Component + /// + /// Handles migration of legacy user data from osu-stable. + /// + public class LeagcyImportManager : Component { [Resolved] private SkinManager skins { get; set; } @@ -51,9 +54,9 @@ namespace osu.Game.Database var stableStorage = await getStableStorage().ConfigureAwait(false); var importTasks = new List(); - var beatmapImporter = new StableBeatmapImporter(beatmaps); - var skinImporter = new StableSkinImporter(skins); - var scoreImporter = new StableScoreImporter(scores); + var beatmapImporter = new LegacyBeatmapImporter(beatmaps); + var skinImporter = new LegacySkinImporter(skins); + var scoreImporter = new LegacyScoreImporter(scores); Task beatmapImportTask = Task.CompletedTask; if (content.HasFlagFast(StableContent.Beatmaps)) diff --git a/osu.Game/Database/StableBeatmapImporter.cs b/osu.Game/Database/LegacyBeatmapImporter.cs similarity index 74% rename from osu.Game/Database/StableBeatmapImporter.cs rename to osu.Game/Database/LegacyBeatmapImporter.cs index 7aaa8ba013..a377c39b90 100644 --- a/osu.Game/Database/StableBeatmapImporter.cs +++ b/osu.Game/Database/LegacyBeatmapImporter.cs @@ -4,15 +4,15 @@ using osu.Game.IO; namespace osu.Game.Database { - public class StableBeatmapImporter : StableImporter + public class LegacyBeatmapImporter : LegacyImporter { protected override string ImportFromStablePath => "."; protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage(); - public StableBeatmapImporter(IModelImporter importer) + public LegacyBeatmapImporter(IModelImporter importer) : base(importer) { } } -} \ No newline at end of file +} diff --git a/osu.Game/Database/StableImporter.cs b/osu.Game/Database/LegacyImporter.cs similarity index 92% rename from osu.Game/Database/StableImporter.cs rename to osu.Game/Database/LegacyImporter.cs index e56737959e..4480aa9ee8 100644 --- a/osu.Game/Database/StableImporter.cs +++ b/osu.Game/Database/LegacyImporter.cs @@ -11,9 +11,9 @@ using osu.Game.IO; namespace osu.Game.Database { /// - /// A class which handled importing various user data from osu-stable. + /// A class which handles importing legacy user data of a single type from osu-stable. /// - public class StableImporter + public abstract class LegacyImporter where TModel : class { /// @@ -29,7 +29,7 @@ namespace osu.Game.Database protected readonly IModelImporter Importer; - public StableImporter(IModelImporter importer) + protected LegacyImporter(IModelImporter importer) { Importer = importer; } diff --git a/osu.Game/Database/StableScoreImporter.cs b/osu.Game/Database/LegacyScoreImporter.cs similarity index 83% rename from osu.Game/Database/StableScoreImporter.cs rename to osu.Game/Database/LegacyScoreImporter.cs index fede10c7ea..4cc2be60e6 100644 --- a/osu.Game/Database/StableScoreImporter.cs +++ b/osu.Game/Database/LegacyScoreImporter.cs @@ -7,7 +7,7 @@ using osu.Game.Scoring; namespace osu.Game.Database { - public class StableScoreImporter : StableImporter + public class LegacyScoreImporter : LegacyImporter { protected override string ImportFromStablePath => Path.Combine("Data", "r"); @@ -15,7 +15,7 @@ namespace osu.Game.Database => storage.GetFiles(ImportFromStablePath).Where(p => Importer.HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) .Select(path => storage.GetFullPath(path)); - public StableScoreImporter(IModelImporter importer) + public LegacyScoreImporter(IModelImporter importer) : base(importer) { } diff --git a/osu.Game/Database/StableSkinImporter.cs b/osu.Game/Database/LegacySkinImporter.cs similarity index 60% rename from osu.Game/Database/StableSkinImporter.cs rename to osu.Game/Database/LegacySkinImporter.cs index 65601e85b7..357da1b005 100644 --- a/osu.Game/Database/StableSkinImporter.cs +++ b/osu.Game/Database/LegacySkinImporter.cs @@ -2,11 +2,11 @@ using osu.Game.Skinning; namespace osu.Game.Database { - public class StableSkinImporter : StableImporter + public class LegacySkinImporter : LegacyImporter { protected override string ImportFromStablePath => "Skins"; - public StableSkinImporter(IModelImporter importer) + public LegacySkinImporter(IModelImporter importer) : base(importer) { } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 574a5e5393..9b9ec585e8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -116,7 +116,7 @@ namespace osu.Game private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender(); [Cached] - private readonly StableImportManager stableImportManager = new StableImportManager(); + private readonly LeagcyImportManager leagcyImportManager = new LeagcyImportManager(); [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); @@ -782,7 +782,7 @@ namespace osu.Game PostNotification = n => Notifications.Post(n), }, Add, true); - loadComponentSingleFile(stableImportManager, Add); + loadComponentSingleFile(leagcyImportManager, Add); loadComponentSingleFile(screenshotManager, Add); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 43df58a8b1..9dfd0d8ff4 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -31,9 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton undeleteButton; [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] LeagcyImportManager leagcyImportManager, DialogOverlay dialogOverlay) { - if (stableImportManager?.SupportsImportFromStable == true) + if (leagcyImportManager?.SupportsImportFromStable == true) { Add(importBeatmapsButton = new SettingsButton { @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importBeatmapsButton.Enabled.Value = false; - stableImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); + leagcyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); } @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (stableImportManager?.SupportsImportFromStable == true) + if (leagcyImportManager?.SupportsImportFromStable == true) { Add(importScoresButton = new SettingsButton { @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importScoresButton.Enabled.Value = false; - stableImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + leagcyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); } }); } @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (stableImportManager?.SupportsImportFromStable == true) + if (leagcyImportManager?.SupportsImportFromStable == true) { Add(importSkinsButton = new SettingsButton { @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importSkinsButton.Enabled.Value = false; - stableImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + leagcyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); } }); } @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager != null) { - if (stableImportManager?.SupportsImportFromStable == true) + if (leagcyImportManager?.SupportsImportFromStable == true) { Add(importCollectionsButton = new SettingsButton { @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - stableImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + leagcyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2c36bf5fc8..b6933a9830 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; - protected virtual bool DisplayStableImportPrompt => stableImportManager?.SupportsImportFromStable == true; + protected virtual bool DisplayStableImportPrompt => leagcyImportManager?.SupportsImportFromStable == true; public override bool? AllowTrackAdjustments => true; @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select private BeatmapManager beatmaps { get; set; } [Resolved(CanBeNull = true)] - private StableImportManager stableImportManager { get; set; } + private LeagcyImportManager leagcyImportManager { get; set; } protected ModSelectOverlay ModSelect { get; private set; } @@ -297,7 +297,7 @@ namespace osu.Game.Screens.Select { dialogOverlay.Push(new ImportFromStablePopup(() => { - Task.Run(() => stableImportManager.ImportFromStableAsync(StableContent.All)); + Task.Run(() => leagcyImportManager.ImportFromStableAsync(StableContent.All)); })); } }); From 2df793ca228e69032fcb0e8027ebd4bf4a138589 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 15:44:04 +0900 Subject: [PATCH 21/52] Inline individual importers to avoid unnecessary construction for singular import types --- osu.Game/Database/LeagcyImportManager.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/LeagcyImportManager.cs b/osu.Game/Database/LeagcyImportManager.cs index 9f3f9d007a..8ee63429e2 100644 --- a/osu.Game/Database/LeagcyImportManager.cs +++ b/osu.Game/Database/LeagcyImportManager.cs @@ -54,22 +54,18 @@ namespace osu.Game.Database var stableStorage = await getStableStorage().ConfigureAwait(false); var importTasks = new List(); - var beatmapImporter = new LegacyBeatmapImporter(beatmaps); - var skinImporter = new LegacySkinImporter(skins); - var scoreImporter = new LegacyScoreImporter(scores); - Task beatmapImportTask = Task.CompletedTask; if (content.HasFlagFast(StableContent.Beatmaps)) - importTasks.Add(beatmapImportTask = beatmapImporter.ImportFromStableAsync(stableStorage)); + importTasks.Add(beatmapImportTask = new LegacyBeatmapImporter(beatmaps).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Skins)) - importTasks.Add(skinImporter.ImportFromStableAsync(stableStorage)); + importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => scoreImporter.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); } From e6cfe44652ac475f733135863458ac569487b7d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 16:27:55 +0900 Subject: [PATCH 22/52] Fix occasional test failure due to default value oversight in `TestSceneBeatmapCarousel` --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 5906979bc4..f637c715a1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -572,7 +572,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add mixed ruleset beatmapset", () => { - testMixed = TestResources.CreateTestBeatmapSetInfo(); + testMixed = TestResources.CreateTestBeatmapSetInfo(3); for (int i = 0; i <= 2; i++) { @@ -595,7 +595,7 @@ namespace osu.Game.Tests.Visual.SongSelect BeatmapSetInfo testSingle = null; AddStep("add single ruleset beatmapset", () => { - testSingle = TestResources.CreateTestBeatmapSetInfo(); + testSingle = TestResources.CreateTestBeatmapSetInfo(3); testSingle.Beatmaps.ForEach(b => { b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); @@ -615,7 +615,7 @@ namespace osu.Game.Tests.Visual.SongSelect List manySets = new List(); for (int i = 1; i <= 50; i++) - manySets.Add(TestResources.CreateTestBeatmapSetInfo(i)); + manySets.Add(TestResources.CreateTestBeatmapSetInfo(3)); loadBeatmaps(manySets); From a0fa030f55385cb3f85d1a2d4c1d8e99e65cbb07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 16:33:04 +0900 Subject: [PATCH 23/52] Rename base class to `LegacyModelImporter` --- osu.Game/Database/LegacyBeatmapImporter.cs | 2 +- .../Database/{LegacyImporter.cs => LegacyModelImporter.cs} | 4 ++-- osu.Game/Database/LegacyScoreImporter.cs | 2 +- osu.Game/Database/LegacySkinImporter.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Database/{LegacyImporter.cs => LegacyModelImporter.cs} (95%) diff --git a/osu.Game/Database/LegacyBeatmapImporter.cs b/osu.Game/Database/LegacyBeatmapImporter.cs index a377c39b90..041a692655 100644 --- a/osu.Game/Database/LegacyBeatmapImporter.cs +++ b/osu.Game/Database/LegacyBeatmapImporter.cs @@ -4,7 +4,7 @@ using osu.Game.IO; namespace osu.Game.Database { - public class LegacyBeatmapImporter : LegacyImporter + public class LegacyBeatmapImporter : LegacyModelImporter { protected override string ImportFromStablePath => "."; diff --git a/osu.Game/Database/LegacyImporter.cs b/osu.Game/Database/LegacyModelImporter.cs similarity index 95% rename from osu.Game/Database/LegacyImporter.cs rename to osu.Game/Database/LegacyModelImporter.cs index 4480aa9ee8..dacb7327ea 100644 --- a/osu.Game/Database/LegacyImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -13,7 +13,7 @@ namespace osu.Game.Database /// /// A class which handles importing legacy user data of a single type from osu-stable. /// - public abstract class LegacyImporter + public abstract class LegacyModelImporter where TModel : class { /// @@ -29,7 +29,7 @@ namespace osu.Game.Database protected readonly IModelImporter Importer; - protected LegacyImporter(IModelImporter importer) + protected LegacyModelImporter(IModelImporter importer) { Importer = importer; } diff --git a/osu.Game/Database/LegacyScoreImporter.cs b/osu.Game/Database/LegacyScoreImporter.cs index 4cc2be60e6..984aa8a1ac 100644 --- a/osu.Game/Database/LegacyScoreImporter.cs +++ b/osu.Game/Database/LegacyScoreImporter.cs @@ -7,7 +7,7 @@ using osu.Game.Scoring; namespace osu.Game.Database { - public class LegacyScoreImporter : LegacyImporter + public class LegacyScoreImporter : LegacyModelImporter { protected override string ImportFromStablePath => Path.Combine("Data", "r"); diff --git a/osu.Game/Database/LegacySkinImporter.cs b/osu.Game/Database/LegacySkinImporter.cs index 357da1b005..b102e26bc6 100644 --- a/osu.Game/Database/LegacySkinImporter.cs +++ b/osu.Game/Database/LegacySkinImporter.cs @@ -2,7 +2,7 @@ using osu.Game.Skinning; namespace osu.Game.Database { - public class LegacySkinImporter : LegacyImporter + public class LegacySkinImporter : LegacyModelImporter { protected override string ImportFromStablePath => "Skins"; From 416ee2447a02f60b29d39b19d65851628b139236 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 16:33:35 +0900 Subject: [PATCH 24/52] Move archive filename helper method to extension method --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Extensions/ModelExtensions.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 63cdd0b852..c91264c03a 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -212,7 +212,7 @@ namespace osu.Game.Beatmaps 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. - 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(); diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index 9f00d21383..2274da0fd4 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; @@ -124,5 +125,21 @@ namespace osu.Game.Extensions 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(); + + /// + /// Get a valid filename for use inside a zip file. Avoids backslashes being incorrectly converted to directories. + /// + public static string GetValidArchiveContentFilename(this string filename) + { + foreach (char c in invalid_filename_characters) + filename = filename.Replace(c, '_'); + return filename; + } } } From 7488ccd5fe4830469b9e8e1bc393ea1a0e0c7348 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 16:35:42 +0900 Subject: [PATCH 25/52] Update all models to implement `IHasNamedFiles` --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Beatmaps/IBeatmapSetInfo.cs | 7 +------ osu.Game/Database/IHasNamedFiles.cs | 15 +++++++++++++++ osu.Game/Models/RealmBeatmapSet.cs | 3 +-- .../API/Requests/Responses/APIBeatmapSet.cs | 2 +- .../Online/API/Requests/Responses/APIScoreInfo.cs | 2 ++ osu.Game/Scoring/IScoreInfo.cs | 2 +- osu.Game/Scoring/ScoreFileInfo.cs | 4 +++- osu.Game/Scoring/ScoreInfo.cs | 2 ++ osu.Game/Skinning/SkinFileInfo.cs | 4 +++- osu.Game/Skinning/SkinInfo.cs | 4 +++- 11 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Database/IHasNamedFiles.cs diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a4d60d7ea0..ac7067edda 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata ?? Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; - IEnumerable IBeatmapSetInfo.Files => Files; + IEnumerable IHasNamedFiles.Files => Files; #endregion } diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs index aa114c8472..9755120457 100644 --- a/osu.Game/Beatmaps/IBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps /// /// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive. /// - public interface IBeatmapSetInfo : IHasOnlineID, IEquatable + public interface IBeatmapSetInfo : IHasOnlineID, IEquatable, IHasNamedFiles { /// /// The date when this beatmap was imported. @@ -29,11 +29,6 @@ namespace osu.Game.Beatmaps /// IEnumerable Beatmaps { get; } - /// - /// All files used by this set. - /// - IEnumerable Files { get; } - /// /// The maximum star difficulty of all beatmaps in this set. /// diff --git a/osu.Game/Database/IHasNamedFiles.cs b/osu.Game/Database/IHasNamedFiles.cs new file mode 100644 index 0000000000..08906aaa08 --- /dev/null +++ b/osu.Game/Database/IHasNamedFiles.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . 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 + { + /// + /// All files used by this model. + /// + IEnumerable Files { get; } + } +} diff --git a/osu.Game/Models/RealmBeatmapSet.cs b/osu.Game/Models/RealmBeatmapSet.cs index fee59633f1..3566ff5321 100644 --- a/osu.Game/Models/RealmBeatmapSet.cs +++ b/osu.Game/Models/RealmBeatmapSet.cs @@ -76,7 +76,6 @@ namespace osu.Game.Models public bool Equals(IBeatmapSetInfo? other) => other is RealmBeatmapSet b && Equals(b); IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; - - IEnumerable IBeatmapSetInfo.Files => Files; + IEnumerable IHasNamedFiles.Files => Files; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 9005fa8eb7..57c45faed3 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -136,7 +136,7 @@ namespace osu.Game.Online.API.Requests.Responses IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => metadata; DateTimeOffset IBeatmapSetInfo.DateAdded => throw new NotImplementedException(); - IEnumerable IBeatmapSetInfo.Files => throw new NotImplementedException(); + IEnumerable IHasNamedFiles.Files => throw new NotImplementedException(); double IBeatmapSetInfo.MaxStarDifficulty => throw new NotImplementedException(); double IBeatmapSetInfo.MaxLength => throw new NotImplementedException(); double IBeatmapSetInfo.MaxBPM => BPM; diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 0a2d6ca7b0..467d5a9f23 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -147,6 +148,7 @@ namespace osu.Game.Online.API.Requests.Responses } public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; + IEnumerable IHasNamedFiles.Files => throw new NotImplementedException(); IBeatmapInfo IScoreInfo.Beatmap => Beatmap; } diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index 21a402f8c3..8b5b228632 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets; namespace osu.Game.Scoring { - public interface IScoreInfo : IHasOnlineID + public interface IScoreInfo : IHasOnlineID, IHasNamedFiles { APIUser User { get; } diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs index 9075fdec5b..b2e81d4b8d 100644 --- a/osu.Game/Scoring/ScoreFileInfo.cs +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -7,7 +7,7 @@ using osu.Game.IO; namespace osu.Game.Scoring { - public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey + public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage { public int ID { get; set; } @@ -17,5 +17,7 @@ namespace osu.Game.Scoring [Required] public string Filename { get; set; } + + IFileInfo INamedFileUsage.File => FileInfo; } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index b4e194cbed..564aa3b98c 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -257,5 +257,7 @@ namespace osu.Game.Scoring bool IScoreInfo.HasReplay => Files.Any(); #endregion + + IEnumerable IHasNamedFiles.Files => Files; } } diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs index 8a7019e1a3..db7cd953bb 100644 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -7,7 +7,7 @@ using osu.Game.IO; namespace osu.Game.Skinning { - public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey + public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage { public int ID { get; set; } @@ -19,5 +19,7 @@ namespace osu.Game.Skinning [Required] public string Filename { get; set; } + + IFileInfo INamedFileUsage.File => FileInfo; } } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 4733e8cdd9..5d2d51a9b0 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -10,7 +10,7 @@ using osu.Game.IO; namespace osu.Game.Skinning { - public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete + public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete, IHasNamedFiles { internal const int DEFAULT_SKIN = 0; internal const int CLASSIC_SKIN = -1; @@ -55,5 +55,7 @@ namespace osu.Game.Skinning string author = Creator == null ? string.Empty : $"({Creator})"; return $"{Name} {author}".Trim(); } + + IEnumerable IHasNamedFiles.Files => Files; } } From cc1b91e4bd9aa7730771088da568d841a22eaf72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 16:36:30 +0900 Subject: [PATCH 26/52] Split out legacy model export logic into `LegacyModelExporter` classes --- osu.Game/Beatmaps/BeatmapManager.cs | 10 --- osu.Game/Database/ArchiveModelManager.cs | 53 --------------- osu.Game/Database/IModelManager.cs | 14 ---- osu.Game/Database/LegacyBeatmapExporter.cs | 18 +++++ osu.Game/Database/LegacyExporter.cs | 66 +++++++++++++++++++ osu.Game/Database/LegacyScoreExporter.cs | 31 +++++++++ osu.Game/Database/LegacySkinExporter.cs | 18 +++++ .../Online/Leaderboards/LeaderboardScore.cs | 7 +- .../Overlays/Settings/Sections/SkinSection.cs | 7 +- osu.Game/Scoring/ScoreManager.cs | 10 --- osu.Game/Scoring/ScoreModelManager.cs | 12 ---- osu.Game/Screens/Edit/Editor.cs | 7 +- osu.Game/Skinning/SkinManager.cs | 10 --- 13 files changed, 151 insertions(+), 112 deletions(-) create mode 100644 osu.Game/Database/LegacyBeatmapExporter.cs create mode 100644 osu.Game/Database/LegacyExporter.cs create mode 100644 osu.Game/Database/LegacyScoreExporter.cs create mode 100644 osu.Game/Database/LegacySkinExporter.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2cca3ceaeb..1c01f100eb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -225,16 +225,6 @@ namespace osu.Game.Beatmaps remove => beatmapModelManager.ItemRemoved -= value; } - public void Export(BeatmapSetInfo item) - { - beatmapModelManager.Export(item); - } - - public void ExportModelTo(BeatmapSetInfo model, Stream outputStream) - { - beatmapModelManager.ExportModelTo(model, outputStream); - } - public void Update(BeatmapSetInfo item) { beatmapModelManager.Update(item); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e5919cb5c4..e73f4a7f6e 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -20,7 +20,6 @@ using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.IPC; using osu.Game.Overlays.Notifications; -using SharpCompress.Archives.Zip; 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) private ArchiveImportIPCChannel ipc; - private readonly Storage exportStorage; - protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; @@ -92,8 +89,6 @@ namespace osu.Game.Database ModelStore.ItemUpdated += item => handleEvent(() => ItemUpdated?.Invoke(item)); ModelStore.ItemRemoved += item => handleEvent(() => ItemRemoved?.Invoke(item)); - exportStorage = storage.GetStorageForDirectory(@"exports"); - Files = new FileStore(contextFactory, storage); if (importHost != null) @@ -452,41 +447,6 @@ namespace osu.Game.Database return item.ToEntityFrameworkLive(); }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false); - /// - /// Exports an item to a legacy (.zip based) package. - /// - /// The item to export. - 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); - } - - /// - /// Exports an item to the given output stream. - /// - /// The item to export. - /// The output stream to export to. - 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); - } - } - /// /// Replace an existing file with a new version. /// @@ -875,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. 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; - } } } diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 6c9cca7c7a..779d0522f7 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; namespace osu.Game.Database { @@ -24,19 +23,6 @@ namespace osu.Game.Database /// event Action ItemRemoved; - /// - /// Exports an item to a legacy (.zip based) package. - /// - /// The item to export. - void Export(TModel item); - - /// - /// Exports an item to the given output stream. - /// - /// The item to export. - /// The output stream to export to. - void ExportModelTo(TModel model, Stream outputStream); - /// /// Perform an update of the specified item. /// TODO: Support file additions/removals. diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs new file mode 100644 index 0000000000..671ed54326 --- /dev/null +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . 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 + { + protected override string FileExtension => ".osz"; + + public LegacyBeatmapExporter(Storage storage, IModelManager manager) + : base(storage, manager) + { + } + } +} diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs new file mode 100644 index 0000000000..846495b1ed --- /dev/null +++ b/osu.Game/Database/LegacyExporter.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . 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 +{ + /// + /// A class which handles exporting legacy user data of a single type from osu-stable. + /// + public abstract class LegacyExporter + where TModel : class, IHasNamedFiles + { + /// + /// The file extension for exports (including the leading '.'). + /// + protected abstract string FileExtension { get; } + + protected readonly IModelManager Manager; + + protected readonly Storage UserFileStorage; + + private readonly Storage exportStorage; + + protected LegacyExporter(Storage storage, IModelManager manager) + { + Manager = manager; + + exportStorage = storage.GetStorageForDirectory(@"exports"); + UserFileStorage = storage.GetStorageForDirectory(@"files"); + } + + /// + /// Exports an item to a legacy (.zip based) package. + /// + /// The item to export. + 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); + } + + /// + /// Exports an item to the given output stream. + /// + /// The item to export. + /// The output stream to export to. + 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); + } + } + } +} diff --git a/osu.Game/Database/LegacyScoreExporter.cs b/osu.Game/Database/LegacyScoreExporter.cs new file mode 100644 index 0000000000..e4ddd5982b --- /dev/null +++ b/osu.Game/Database/LegacyScoreExporter.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . 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 + { + protected override string FileExtension => ".osr"; + + public LegacyScoreExporter(Storage storage, IModelManager manager) + : base(storage, manager) + { + } + + 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); + } + } +} diff --git a/osu.Game/Database/LegacySkinExporter.cs b/osu.Game/Database/LegacySkinExporter.cs new file mode 100644 index 0000000000..f6d243315c --- /dev/null +++ b/osu.Game/Database/LegacySkinExporter.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . 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 + { + protected override string FileExtension => ".osk"; + + public LegacySkinExporter(Storage storage, IModelManager manager) + : base(storage, manager) + { + } + } +} diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f0c57cb953..06c0636d17 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -14,6 +14,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -66,6 +68,9 @@ namespace osu.Game.Online.Leaderboards [Resolved] private ScoreManager scoreManager { get; set; } + [Resolved] + private Storage storage { get; set; } + public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { 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)); 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, scoreManager).Export(Score))); if (Score.ID != 0) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index e7a7abed59..0eec6f5f77 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -11,7 +11,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Skinning; @@ -167,6 +169,9 @@ namespace osu.Game.Overlays.Settings.Sections [Resolved] private SkinManager skins { get; set; } + [Resolved] + private Storage storage { get; set; } + private Bindable currentSkin; [BackgroundDependencyLoader] @@ -183,7 +188,7 @@ namespace osu.Game.Overlays.Settings.Sections { try { - skins.Export(currentSkin.Value.SkinInfo); + new LegacySkinExporter(storage, skins).Export(currentSkin.Value.SkinInfo); } catch (Exception e) { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d25671d77e..a89444408d 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -262,16 +262,6 @@ namespace osu.Game.Scoring remove => scoreModelManager.ItemRemoved -= value; } - public void Export(ScoreInfo item) - { - scoreModelManager.Export(item); - } - - public void ExportModelTo(ScoreInfo model, Stream outputStream) - { - scoreModelManager.ExportModelTo(model, outputStream); - } - public void Update(ScoreInfo item) { scoreModelManager.Update(item); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 9da739237b..2cbd3aded7 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading; @@ -13,7 +12,6 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; @@ -69,15 +67,5 @@ namespace osu.Game.Scoring protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) || (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); - } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 94b6e58b67..46d84fe73d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -16,9 +16,11 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -63,6 +65,9 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved] + private Storage storage { get; set; } + [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } @@ -753,7 +758,7 @@ namespace osu.Game.Screens.Edit private void exportBeatmap() { Save(); - beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); + new LegacyBeatmapExporter(storage, beatmapManager).Export(Beatmap.Value.BeatmapSetInfo); } private void updateLastSavedHash() diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 679b35799e..8d07dd046a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -301,16 +301,6 @@ namespace osu.Game.Skinning remove => skinModelManager.ItemRemoved -= value; } - public void Export(SkinInfo item) - { - skinModelManager.Export(item); - } - - public void ExportModelTo(SkinInfo model, Stream outputStream) - { - skinModelManager.ExportModelTo(model, outputStream); - } - public void Update(SkinInfo item) { skinModelManager.Update(item); From ec9a09d5a42a0a2818c369faf22e134802e73917 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 16:56:19 +0900 Subject: [PATCH 27/52] Add missing licence headers --- osu.Game/Database/LegacyBeatmapImporter.cs | 3 +++ osu.Game/Database/LegacyScoreImporter.cs | 3 +++ osu.Game/Database/LegacySkinImporter.cs | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyBeatmapImporter.cs b/osu.Game/Database/LegacyBeatmapImporter.cs index 041a692655..97f6eba6c2 100644 --- a/osu.Game/Database/LegacyBeatmapImporter.cs +++ b/osu.Game/Database/LegacyBeatmapImporter.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . 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; diff --git a/osu.Game/Database/LegacyScoreImporter.cs b/osu.Game/Database/LegacyScoreImporter.cs index 984aa8a1ac..48445b7bdb 100644 --- a/osu.Game/Database/LegacyScoreImporter.cs +++ b/osu.Game/Database/LegacyScoreImporter.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . 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; diff --git a/osu.Game/Database/LegacySkinImporter.cs b/osu.Game/Database/LegacySkinImporter.cs index b102e26bc6..2f05ccae45 100644 --- a/osu.Game/Database/LegacySkinImporter.cs +++ b/osu.Game/Database/LegacySkinImporter.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . 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 @@ -11,4 +14,4 @@ namespace osu.Game.Database { } } -} \ No newline at end of file +} From 79459c1aebc968ac4821161399126075b6301c93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 17:12:15 +0900 Subject: [PATCH 28/52] Fix typo in class and variable names --- ...ImportManager.cs => LegacyImportManager.cs} | 2 +- osu.Game/OsuGame.cs | 4 ++-- .../Sections/Maintenance/GeneralSettings.cs | 18 +++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game/Database/{LeagcyImportManager.cs => LegacyImportManager.cs} (98%) diff --git a/osu.Game/Database/LeagcyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs similarity index 98% rename from osu.Game/Database/LeagcyImportManager.cs rename to osu.Game/Database/LegacyImportManager.cs index 8ee63429e2..4dc26b18bb 100644 --- a/osu.Game/Database/LeagcyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Database /// /// Handles migration of legacy user data from osu-stable. /// - public class LeagcyImportManager : Component + public class LegacyImportManager : Component { [Resolved] private SkinManager skins { get; set; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9b9ec585e8..0e050304f0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -116,7 +116,7 @@ namespace osu.Game private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender(); [Cached] - private readonly LeagcyImportManager leagcyImportManager = new LeagcyImportManager(); + private readonly LegacyImportManager legacyImportManager = new LegacyImportManager(); [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); @@ -782,7 +782,7 @@ namespace osu.Game PostNotification = n => Notifications.Post(n), }, Add, true); - loadComponentSingleFile(leagcyImportManager, Add); + loadComponentSingleFile(legacyImportManager, Add); loadComponentSingleFile(screenshotManager, Add); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 9dfd0d8ff4..5bc89ec77c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -31,9 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton undeleteButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LeagcyImportManager leagcyImportManager, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, DialogOverlay dialogOverlay) { - if (leagcyImportManager?.SupportsImportFromStable == true) + if (legacyImportManager?.SupportsImportFromStable == true) { Add(importBeatmapsButton = new SettingsButton { @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importBeatmapsButton.Enabled.Value = false; - leagcyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); } @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (leagcyImportManager?.SupportsImportFromStable == true) + if (legacyImportManager?.SupportsImportFromStable == true) { Add(importScoresButton = new SettingsButton { @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importScoresButton.Enabled.Value = false; - leagcyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); } }); } @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (leagcyImportManager?.SupportsImportFromStable == true) + if (legacyImportManager?.SupportsImportFromStable == true) { Add(importSkinsButton = new SettingsButton { @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importSkinsButton.Enabled.Value = false; - leagcyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); } }); } @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager != null) { - if (leagcyImportManager?.SupportsImportFromStable == true) + if (legacyImportManager?.SupportsImportFromStable == true) { Add(importCollectionsButton = new SettingsButton { @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - leagcyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b6933a9830..25efe22892 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; - protected virtual bool DisplayStableImportPrompt => leagcyImportManager?.SupportsImportFromStable == true; + protected virtual bool DisplayStableImportPrompt => legacyImportManager?.SupportsImportFromStable == true; public override bool? AllowTrackAdjustments => true; @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select private BeatmapManager beatmaps { get; set; } [Resolved(CanBeNull = true)] - private LeagcyImportManager leagcyImportManager { get; set; } + private LegacyImportManager legacyImportManager { get; set; } protected ModSelectOverlay ModSelect { get; private set; } @@ -297,7 +297,7 @@ namespace osu.Game.Screens.Select { dialogOverlay.Push(new ImportFromStablePopup(() => { - Task.Run(() => leagcyImportManager.ImportFromStableAsync(StableContent.All)); + Task.Run(() => legacyImportManager.ImportFromStableAsync(StableContent.All)); })); } }); From 716543b5b3ba8bb467da72ae059a153fc449a813 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 17:23:46 +0900 Subject: [PATCH 29/52] Move beatmap download logic out of `BeatmapManager` --- ...ager.cs => TestSceneBeatmapDownloading.cs} | 6 +-- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 35 ++++++++---------- osu.Game/Beatmaps/BeatmapManager.cs | 37 +------------------ .../Drawables/Cards/Buttons/DownloadButton.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 19 +++++++--- osu.Game/OsuGame.cs | 1 + osu.Game/OsuGameBase.cs | 3 ++ .../Panels/BeatmapPanelDownloadButton.cs | 2 +- .../Buttons/HeaderDownloadButton.cs | 2 +- osu.Game/Screens/Play/SoloSpectator.cs | 5 ++- 10 files changed, 45 insertions(+), 67 deletions(-) rename osu.Game.Tests/Online/{TestSceneBeatmapManager.cs => TestSceneBeatmapDownloading.cs} (94%) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs similarity index 94% rename from osu.Game.Tests/Online/TestSceneBeatmapManager.cs rename to osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs index fc1b4f224d..4e77973655 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs @@ -12,9 +12,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public class TestSceneBeatmapManager : OsuTestScene + public class TestSceneBeatmapDownloading : OsuTestScene { - private BeatmapManager beatmaps; + private BeatmapModelDownloader beatmaps; private ProgressNotification recentNotification; private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Online }; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) + private void load(BeatmapModelDownloader beatmaps) { this.beatmaps = beatmaps; diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index a4c69075be..f5d57240ca 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -33,6 +33,7 @@ namespace osu.Game.Tests.Online { private RulesetStore rulesets; private TestBeatmapManager beatmaps; + private TestBeatmapModelDownloader beatmapDownloader; private string testBeatmapFile; private BeatmapInfo testBeatmapInfo; @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Online { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } [SetUp] @@ -80,13 +82,13 @@ namespace osu.Game.Tests.Online AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); 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)); - 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)); - AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); + AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); 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); } - protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter manager, IAPIProvider api, GameHost host) - { - return new TestBeatmapModelDownloader(manager, api, host); - } - - internal class TestBeatmapModelDownloader : BeatmapModelDownloader - { - public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) - : base(importer, apiProvider, gameHost) - { - } - - protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) - => new TestDownloadRequest(set); - } - internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; @@ -205,6 +191,17 @@ namespace osu.Game.Tests.Online } } + internal class TestBeatmapModelDownloader : BeatmapModelDownloader + { + public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) + : base(importer, apiProvider, gameHost) + { + } + + protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) + => new TestDownloadRequest(set); + } + private class TestDownloadRequest : ArchiveDownloadRequest { public new void SetProgress(float progress) => base.SetProgress(progress); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0594cd1316..179d479b53 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -29,12 +29,11 @@ namespace osu.Game.Beatmaps /// Handles general operations related to global beatmap management. /// [ExcludeFromDynamicCompile] - public class BeatmapManager : IModelDownloader, IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable + public class BeatmapManager : IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable { public ITrackStore BeatmapTrackStore { get; } private readonly BeatmapModelManager beatmapModelManager; - private readonly BeatmapModelDownloader beatmapModelDownloader; private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; @@ -46,7 +45,6 @@ namespace osu.Game.Beatmaps BeatmapTrackStore = audioManager.GetTrackStore(userResources); beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host); - beatmapModelDownloader = CreateBeatmapModelDownloader(beatmapModelManager, api, host); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache.BeatmapManager = beatmapModelManager; @@ -59,11 +57,6 @@ namespace osu.Game.Beatmaps } } - protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter modelManager, IAPIProvider api, GameHost host) - { - return new BeatmapModelDownloader(modelManager, api, host); - } - protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) { return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); @@ -185,11 +178,7 @@ namespace osu.Game.Beatmaps /// public Action PostNotification { - set - { - beatmapModelManager.PostNotification = value; - beatmapModelDownloader.PostNotification = value; - } + set => beatmapModelManager.PostNotification = value; } /// @@ -267,28 +256,6 @@ namespace osu.Game.Beatmaps #endregion - #region Implementation of IModelDownloader - - public event Action> DownloadBegan - { - add => beatmapModelDownloader.DownloadBegan += value; - remove => beatmapModelDownloader.DownloadBegan -= value; - } - - public event Action> DownloadFailed - { - add => beatmapModelDownloader.DownloadFailed += value; - remove => beatmapModelDownloader.DownloadFailed -= value; - } - - public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false) => - beatmapModelDownloader.Download(model, minimiseDownloadSize); - - public ArchiveDownloadRequest GetExistingDownload(IBeatmapSetInfo model) => - beatmapModelDownloader.GetExistingDownload(model); - - #endregion - #region Implementation of ICanAcceptFiles public Task Import(params string[] paths) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 7430fce1c8..1a514c8d36 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private Bindable preferNoVideo = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } = null!; + private BeatmapModelDownloader beatmaps { get; set; } = null!; public DownloadButton(APIBeatmapSet beatmapSet) { diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 77a8fca1e4..509d5c1b71 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -15,6 +15,9 @@ namespace osu.Game.Online [Resolved(CanBeNull = true)] protected BeatmapManager? Manager { get; private set; } + [Resolved(CanBeNull = true)] + protected BeatmapModelDownloader? Downloader { get; private set; } + private ArchiveDownloadRequest? attachedRequest; public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem) @@ -25,7 +28,7 @@ namespace osu.Game.Online [BackgroundDependencyLoader(true)] private void load() { - if (Manager == null) + if (Manager == null || Downloader == null) return; // 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)) UpdateState(DownloadState.LocallyAvailable); else - attachDownload(Manager.GetExistingDownload(beatmapSetInfo)); + attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); - Manager.DownloadBegan += downloadBegan; - Manager.DownloadFailed += downloadFailed; + Downloader.DownloadBegan += downloadBegan; + Downloader.DownloadFailed += downloadFailed; Manager.ItemUpdated += itemUpdated; Manager.ItemRemoved += itemRemoved; } @@ -115,10 +118,14 @@ namespace osu.Game.Online base.Dispose(isDisposing); attachDownload(null); + if (Downloader != null) + { + Downloader.DownloadBegan -= downloadBegan; + Downloader.DownloadFailed -= downloadFailed; + } + if (Manager != null) { - Manager.DownloadBegan -= downloadBegan; - Manager.DownloadFailed -= downloadFailed; Manager.ItemUpdated -= itemUpdated; Manager.ItemRemoved -= itemRemoved; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 574a5e5393..1060724680 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -656,6 +656,7 @@ namespace osu.Game BeatmapManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value); + BeatmapDownloader.PostNotification = n => Notifications.Post(n); ScoreManager.PostNotification = n => Notifications.Post(n); ScoreManager.PostImport = items => PresentScore(items.First().Value); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dd4ae590c7..a64d9ac01a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -96,6 +96,8 @@ namespace osu.Game protected BeatmapManager BeatmapManager { get; private set; } + protected BeatmapModelDownloader BeatmapDownloader { get; private set; } + protected ScoreManager ScoreManager { get; private set; } protected SkinManager SkinManager { get; private set; } @@ -235,6 +237,7 @@ namespace osu.Game dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API, Host)); // the following realm components are not actively used yet, but initialised and kept up to date for initial testing. realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 1282a14c3d..0a66c3ccb7 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels } [BackgroundDependencyLoader(true)] - private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig) + private void load(OsuGame game, BeatmapModelDownloader beatmaps, OsuConfigManager osuConfig) { noVideoSetting = osuConfig.GetBindable(OsuSetting.PreferNoVideo); diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index ee40f114d2..4eed8f28f2 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons } [BackgroundDependencyLoader] - private void load(IAPIProvider api, BeatmapManager beatmaps) + private void load(IAPIProvider api, BeatmapModelDownloader beatmaps) { FillFlowContainer textSprites; diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index c65d4af2ae..5ecdb91718 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -49,6 +49,9 @@ namespace osu.Game.Screens.Play [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } + private Container beatmapPanelContainer; private TriangleButton watchButton; private SettingsCheckbox automaticDownload; @@ -244,7 +247,7 @@ namespace osu.Game.Screens.Play if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID })) return; - beatmaps.Download(beatmapSet); + beatmapDownloader.Download(beatmapSet); } public override bool OnExiting(IScreen next) From a2ab9f457d8c883d8a302befd7a3a78241d89174 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 17:21:05 +0900 Subject: [PATCH 30/52] Move score download logic out of `ScoreManager` --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 19 +++++++--- osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 6 ++- osu.Game/Scoring/ScoreManager.cs | 37 ++----------------- .../Screens/Ranking/ReplayDownloadButton.cs | 4 +- 7 files changed, 27 insertions(+), 45 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2a2df777f7..aa36bde030 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), 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; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 7353e47229..9f0f4a6b8b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), 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]; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 32307fc50e..e09cc7c9cd 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -15,6 +15,9 @@ namespace osu.Game.Online [Resolved(CanBeNull = true)] protected ScoreManager? Manager { get; private set; } + [Resolved(CanBeNull = true)] + protected ScoreModelDownloader? Downloader { get; private set; } + private ArchiveDownloadRequest? attachedRequest; public ScoreDownloadTracker(ScoreInfo trackedItem) @@ -25,7 +28,7 @@ namespace osu.Game.Online [BackgroundDependencyLoader(true)] private void load() { - if (Manager == null) + if (Manager == null || Downloader == null) return; // 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)) UpdateState(DownloadState.LocallyAvailable); else - attachDownload(Manager.GetExistingDownload(scoreInfo)); + attachDownload(Downloader.GetExistingDownload(scoreInfo)); - Manager.DownloadBegan += downloadBegan; - Manager.DownloadFailed += downloadFailed; + Downloader.DownloadBegan += downloadBegan; + Downloader.DownloadFailed += downloadFailed; Manager.ItemUpdated += itemUpdated; Manager.ItemRemoved += itemRemoved; } @@ -119,10 +122,14 @@ namespace osu.Game.Online base.Dispose(isDisposing); attachDownload(null); + if (Downloader != null) + { + Downloader.DownloadBegan -= downloadBegan; + Downloader.DownloadFailed -= downloadFailed; + } + if (Manager != null) { - Manager.DownloadBegan -= downloadBegan; - Manager.DownloadFailed -= downloadFailed; Manager.ItemUpdated -= itemUpdated; Manager.ItemRemoved -= itemRemoved; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1060724680..aa9bdac756 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -657,6 +657,8 @@ namespace osu.Game 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.PostImport = items => PresentScore(items.First().Value); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a64d9ac01a..c0594231df 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -100,6 +100,8 @@ namespace osu.Game protected ScoreManager ScoreManager { get; private set; } + protected ScoreModelDownloader ScoreDownloader { get; private set; } + protected SkinManager SkinManager { get; private set; } protected RulesetStore RulesetStore { get; private set; } @@ -234,10 +236,12 @@ namespace osu.Game dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // 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(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API, Host)); + dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API, Host)); + // the following realm components are not actively used yet, but initialised and kept up to date for initial testing. realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index abc0bfdda7..8d9733669c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -17,7 +17,6 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Online.API; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; @@ -25,15 +24,14 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring { - public class ScoreManager : IModelManager, IModelImporter, IModelDownloader + public class ScoreManager : IModelManager, IModelImporter { private readonly Scheduler scheduler; private readonly Func difficulties; private readonly OsuConfigManager configManager; private readonly ScoreModelManager scoreModelManager; - private readonly ScoreModelDownloader scoreModelDownloader; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, Scheduler scheduler, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) { this.scheduler = scheduler; @@ -41,7 +39,6 @@ namespace osu.Game.Scoring this.configManager = configManager; scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory, importHost); - scoreModelDownloader = new ScoreModelDownloader(scoreModelManager, api, importHost); } public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score); @@ -240,11 +237,7 @@ namespace osu.Game.Scoring public Action PostNotification { - set - { - scoreModelManager.PostNotification = value; - scoreModelDownloader.PostNotification = value; - } + set => scoreModelManager.PostNotification = value; } #endregion @@ -342,30 +335,6 @@ namespace osu.Game.Scoring #endregion - #region Implementation of IModelDownloader - - public event Action> DownloadBegan - { - add => scoreModelDownloader.DownloadBegan += value; - remove => scoreModelDownloader.DownloadBegan -= value; - } - - public event Action> DownloadFailed - { - add => scoreModelDownloader.DownloadFailed += value; - remove => scoreModelDownloader.DownloadFailed -= value; - } - - public bool Download(IScoreInfo model, bool minimiseDownloadSize) => - scoreModelDownloader.Download(model, minimiseDownloadSize); - - public ArchiveDownloadRequest GetExistingDownload(IScoreInfo model) - { - return scoreModelDownloader.GetExistingDownload(model); - } - - #endregion - #region Implementation of IPresentImports public Action>> PostImport diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 66b3c973f5..6a74fdaf75 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader(true)] - private void load(OsuGame game, ScoreManager scores) + private void load(OsuGame game, ScoreModelDownloader scores) { InternalChild = shakeContainer = new ShakeContainer { @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Ranking break; case DownloadState.NotDownloaded: - scores.Download(Score.Value, false); + scores.Download(Score.Value); break; case DownloadState.Importing: From eeccf836ec04d2e3aac1fec93c15b48aa6672352 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 17:42:41 +0900 Subject: [PATCH 31/52] Remove unnecessary `GameHost` parameter --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 5 ++--- osu.Game/Database/ModelDownloader.cs | 3 +-- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Scoring/ScoreModelDownloader.cs | 5 ++--- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index f5d57240ca..24824b1e23 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Online internal class TestBeatmapModelDownloader : BeatmapModelDownloader { public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) - : base(importer, apiProvider, gameHost) + : base(importer, apiProvider) { } diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index a170edc9f8..d31730ca15 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -16,8 +15,8 @@ namespace osu.Game.Beatmaps public override ArchiveDownloadRequest GetExistingDownload(IBeatmapSetInfo model) => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); - public BeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api, GameHost host = null) - : base(beatmapImporter, api, host) + public BeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) + : base(beatmapImporter, api) { } } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 43ba62dfe0..362bc68cc1 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Humanizer; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays.Notifications; @@ -29,7 +28,7 @@ namespace osu.Game.Database protected readonly List> CurrentDownloads = new List>(); - protected ModelDownloader(IModelImporter importer, IAPIProvider api, IIpcHost importHost = null) + protected ModelDownloader(IModelImporter importer, IAPIProvider api) { this.importer = importer; this.api = api; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c0594231df..88c9ab370c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -239,8 +239,8 @@ namespace osu.Game 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(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API, Host)); - dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API, Host)); + 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. realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index 6c63e2aa71..038a4bc351 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -10,8 +9,8 @@ namespace osu.Game.Scoring { public class ScoreModelDownloader : ModelDownloader { - public ScoreModelDownloader(IModelImporter scoreManager, IAPIProvider api, IIpcHost importHost = null) - : base(scoreManager, api, importHost) + public ScoreModelDownloader(IModelImporter scoreManager, IAPIProvider api) + : base(scoreManager, api) { } From a6ee0eec0dcaaed2844adf40fa9d7d54f6633ad1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 18:15:09 +0900 Subject: [PATCH 32/52] Remove pointless interface class for `IBeatmapModelManager` Was originally going to be used but serves no purpose any more. --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/IBeatmapModelManager.cs | 20 -------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 osu.Game/Beatmaps/IBeatmapModelManager.cs diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ae395c6da6..fc25884446 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps /// Handles ef-core storage of beatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapModelManager : ArchiveModelManager, IBeatmapModelManager + public class BeatmapModelManager : ArchiveModelManager { /// /// Fired when a single difficulty has been hidden. diff --git a/osu.Game/Beatmaps/IBeatmapModelManager.cs b/osu.Game/Beatmaps/IBeatmapModelManager.cs deleted file mode 100644 index 8c243c2b77..0000000000 --- a/osu.Game/Beatmaps/IBeatmapModelManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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 - { - /// - /// Provide an online lookup queue component to handle populating online beatmap metadata. - /// - BeatmapOnlineLookupQueue OnlineLookupQueue { set; } - - /// - /// Provide a working beatmap cache, used to invalidate entries on changes. - /// - IWorkingBeatmapCache WorkingBeatmapCache { set; } - } -} From e2ebcf7a26f932b5ea0bbd8db285d9c2b46b7971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Nov 2021 18:36:01 +0900 Subject: [PATCH 33/52] Remove unnecessary manager parameter Confused why I added this in the first place.. --- osu.Game/Database/LegacyBeatmapExporter.cs | 4 ++-- osu.Game/Database/LegacyExporter.cs | 6 +----- osu.Game/Database/LegacyScoreExporter.cs | 4 ++-- osu.Game/Database/LegacySkinExporter.cs | 4 ++-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 7 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 671ed54326..fb8ee8f5f5 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -10,8 +10,8 @@ namespace osu.Game.Database { protected override string FileExtension => ".osz"; - public LegacyBeatmapExporter(Storage storage, IModelManager manager) - : base(storage, manager) + public LegacyBeatmapExporter(Storage storage) + : base(storage) { } } diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 846495b1ed..802ccec6ed 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -19,16 +19,12 @@ namespace osu.Game.Database /// protected abstract string FileExtension { get; } - protected readonly IModelManager Manager; - protected readonly Storage UserFileStorage; private readonly Storage exportStorage; - protected LegacyExporter(Storage storage, IModelManager manager) + protected LegacyExporter(Storage storage) { - Manager = manager; - exportStorage = storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); } diff --git a/osu.Game/Database/LegacyScoreExporter.cs b/osu.Game/Database/LegacyScoreExporter.cs index e4ddd5982b..41f8516880 100644 --- a/osu.Game/Database/LegacyScoreExporter.cs +++ b/osu.Game/Database/LegacyScoreExporter.cs @@ -13,8 +13,8 @@ namespace osu.Game.Database { protected override string FileExtension => ".osr"; - public LegacyScoreExporter(Storage storage, IModelManager manager) - : base(storage, manager) + public LegacyScoreExporter(Storage storage) + : base(storage) { } diff --git a/osu.Game/Database/LegacySkinExporter.cs b/osu.Game/Database/LegacySkinExporter.cs index f6d243315c..9432a1b5fc 100644 --- a/osu.Game/Database/LegacySkinExporter.cs +++ b/osu.Game/Database/LegacySkinExporter.cs @@ -10,8 +10,8 @@ namespace osu.Game.Database { protected override string FileExtension => ".osk"; - public LegacySkinExporter(Storage storage, IModelManager manager) - : base(storage, manager) + public LegacySkinExporter(Storage storage) + : base(storage) { } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 06c0636d17..e01c7c9e49 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.Leaderboards items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); if (Score.Files.Count > 0) - items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage, scoreManager).Export(Score))); + items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score))); if (Score.ID != 0) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0eec6f5f77..0eb65b4b0f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -188,7 +188,7 @@ namespace osu.Game.Overlays.Settings.Sections { try { - new LegacySkinExporter(storage, skins).Export(currentSkin.Value.SkinInfo); + new LegacySkinExporter(storage).Export(currentSkin.Value.SkinInfo); } catch (Exception e) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 46d84fe73d..ac71298f36 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -758,7 +758,7 @@ namespace osu.Game.Screens.Edit private void exportBeatmap() { Save(); - new LegacyBeatmapExporter(storage, beatmapManager).Export(Beatmap.Value.BeatmapSetInfo); + new LegacyBeatmapExporter(storage).Export(Beatmap.Value.BeatmapSetInfo); } private void updateLastSavedHash() From 8baf00c0231c3276f497257fd810c36cc3f82109 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 19:36:06 +0900 Subject: [PATCH 34/52] Remove unused using --- osu.Game/Scoring/ScoreManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index cb2659442d..1564b3bcc5 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading; From 09dd0542836a83bc411b99dfb6f46db01b30fbf8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 21:11:13 +0900 Subject: [PATCH 35/52] Fix SongSelect-related test failures --- .../Visual/Editing/TestSceneEditorSaving.cs | 5 ++++- .../Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 +- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Multiplayer/TestScenePlaylistsSongSelect.cs | 2 +- .../Visual/Navigation/TestSceneScreenNavigation.cs | 12 +++++++++--- osu.Game/Screens/Select/SongSelect.cs | 2 ++ 8 files changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index e1e869cfbf..f89be0adf3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -69,7 +69,10 @@ namespace osu.Game.Tests.Visual.Editing 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); AddStep("Open options", () => InputManager.Key(Key.F3)); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index dcb01b83cc..85982682c6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer 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())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index efa5c481ed..1de7289446 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer 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; AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index c4833b5226..027769995a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // edit playlist item AddStep("Press select", () => InputManager.Key(Key.Enter)); - AddUntilStep("wait for song select", () => InputManager.ChildrenOfType().FirstOrDefault() != null); + AddUntilStep("wait for song select", () => InputManager.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); // select beatmap AddStep("Press select", () => InputManager.Key(Key.Enter)); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 6b67a979e5..84b24ba3a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); 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] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 6d761105c1..35c66e8cda 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); 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] diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 497c68cf2e..664c186cf8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -66,7 +66,9 @@ namespace osu.Game.Tests.Visual.Navigation { 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()); @@ -98,7 +100,9 @@ namespace osu.Game.Tests.Visual.Navigation 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()); @@ -130,7 +134,9 @@ namespace osu.Game.Tests.Visual.Navigation 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()); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2c36bf5fc8..045a8fbe54 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -76,6 +76,8 @@ namespace osu.Game.Screens.Select /// public virtual bool AllowEditing => true; + public bool BeatmapSetsLoaded => IsLoaded && Carousel?.BeatmapSetsLoaded == true; + [Resolved] private Bindable> selectedMods { get; set; } From 8a941fa42206becffc9a8059c0ce11f9b76d0806 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 21:41:03 +0900 Subject: [PATCH 36/52] Add owner id to PlaylistItem --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + osu.Game/Online/Rooms/PlaylistItem.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5c339c5d55..4c472164d6 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -718,6 +718,7 @@ namespace osu.Game.Online.Multiplayer var playlistItem = new PlaylistItem { ID = item.ID, + OwnerID = item.OwnerID, Beatmap = { Value = beatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index c889dc514b..a1480865b8 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -18,6 +18,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("id")] public long ID { get; set; } + [JsonProperty("owner_id")] + public int OwnerID { get; set; } + [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } From d0c0b7ce476971c039bf8ad5d6dd9e325b975ea5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 22:05:39 +0900 Subject: [PATCH 37/52] Fix intermittent beatmap thumbnail test --- .../Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index 1b4542d946..a5b52f75f6 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -70,6 +70,11 @@ namespace osu.Game.Tests.Visual.Beatmaps AddWaitStep("wait some", 3); AddAssert("button still visible", () => playButton.IsPresent); + // The track plays in real-time, so we need to check for progress in increments to avoid timeout. + AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.25); + AddUntilStep("progress > 0.5", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.5); + AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.75); + AddUntilStep("wait for track to end", () => !playButton.Playing.Value); AddUntilStep("button hidden", () => !playButton.IsPresent); } From 7c91cd674e2eb01704a512ae6a0facfcea92a028 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 22:17:18 +0900 Subject: [PATCH 38/52] Update test classes to set owner ids --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 6 +++++- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b54e243bde..2d77e17513 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -299,7 +299,7 @@ namespace osu.Game.Tests.Visual.Multiplayer 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(APIRoom != null); @@ -313,6 +313,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case QueueMode.HostOnly: // In host-only mode, the current item is re-used. item.ID = currentItem.ID; + item.OwnerID = currentItem.OwnerID; serverSidePlaylist[currentIndex] = item; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); @@ -323,6 +324,7 @@ namespace osu.Game.Tests.Visual.Multiplayer default: item.ID = serverSidePlaylist.Last().ID + 1; + item.OwnerID = userId; serverSidePlaylist.Add(item); 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 GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index dc12e48297..0b7d9af5e7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -128,8 +128,12 @@ namespace osu.Game.Tests.Visual.OnlinePlay public void AddServerSideRoom(Room room) { room.RoomID.Value ??= currentRoomId++; + for (int i = 0; i < room.Playlist.Count; i++) + { room.Playlist[i].ID = currentPlaylistItemId++; + room.Playlist[i].OwnerID = room.Host.Value.OnlineID; + } serverSideRooms.Add(room); } From 1f5d95666e660557d825f52bb9ef6d0a29c18b6b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 23:15:28 +0900 Subject: [PATCH 39/52] Add owner avatar to multiplayer items --- .../TestSceneDrawableRoomPlaylist.cs | 6 ++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 57 +++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 1d0401832f..b0174104f7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -13,6 +13,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; @@ -34,6 +35,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapManager manager; private RulesetStore rulesets; + [Cached(typeof(UserLookupCache))] + private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -343,6 +347,7 @@ namespace osu.Game.Tests.Visual.Multiplayer playlist.Items.Add(new PlaylistItem { ID = i, + OwnerID = 2, Beatmap = { Value = i % 2 == 1 @@ -390,6 +395,7 @@ namespace osu.Game.Tests.Visual.Multiplayer playlist.Items.Add(new PlaylistItem { ID = index++, + OwnerID = 2, Beatmap = { Value = b }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 5a86161b50..5b90713148 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -4,17 +4,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -26,6 +30,7 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; +using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; @@ -34,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay public class DrawableRoomPlaylistItem : OsuRearrangeableListItem { public const float HEIGHT = 50; + public const float ICON_HEIGHT = 34; public Action RequestDeletion; @@ -45,6 +51,7 @@ namespace osu.Game.Screens.OnlinePlay private LinkFlowContainer authorText; private ExplicitContentBeatmapPill explicitContentPill; private ModDisplay modDisplay; + private UpdateableAvatar ownerAvatar; private readonly IBindable valid = new Bindable(); @@ -54,6 +61,12 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private UserLookupCache userLookupCache { get; set; } + private readonly bool allowEdit; private readonly bool allowSelection; @@ -79,9 +92,6 @@ namespace osu.Game.Screens.OnlinePlay Colour = OsuColour.Gray(0.5f); } - [Resolved] - private OsuColour colours { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -132,7 +142,8 @@ namespace osu.Game.Screens.OnlinePlay maskingContainer.BorderColour = colours.Red; } - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) }; + 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; @@ -183,6 +194,7 @@ namespace osu.Game.Screens.OnlinePlay RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize), @@ -191,12 +203,21 @@ namespace osu.Game.Screens.OnlinePlay { new Drawable[] { + ownerAvatar = new OwnerAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(ICON_HEIGHT), + Margin = new MarginPadding { Left = 8, Right = 8, }, + Masking = true, + CornerRadius = 4, + }, difficultyIconContainer = new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = 8, Right = 8, }, + Margin = new MarginPadding { Right = 8 }, }, new FillFlowContainer { @@ -417,5 +438,31 @@ namespace osu.Game.Screens.OnlinePlay Beatmap.BindValueChanged(beatmap => backgroundSprite.Beatmap.Value = beatmap.NewValue); } } + + private class OwnerAvatar : UpdateableAvatar, IHasTooltip + { + public OwnerAvatar() + { + AddInternal(new TooltipArea(this) + { + RelativeSizeAxes = Axes.Both, + Depth = -1 + }); + } + + public LocalisableString TooltipText => User == null ? "loading user..." : $"queued by {User.Username}"; + + private class TooltipArea : Component, IHasTooltip + { + private readonly OwnerAvatar avatar; + + public TooltipArea(OwnerAvatar avatar) + { + this.avatar = avatar; + } + + public LocalisableString TooltipText => avatar.TooltipText; + } + } } } From 84a36ab4a9c6af5c2a310b4e5233e940fc65f546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 16:48:54 +0200 Subject: [PATCH 40/52] Add integration with beatmap set overlay --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 4ffad0f065..76b1166626 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -41,27 +43,23 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapDownloadTracker downloadTracker; - private BeatmapCardThumbnail thumbnail; - private FillFlowContainer leftIconArea; + private BeatmapCardThumbnail thumbnail = null!; - private Container rightAreaBackground; - private Container rightAreaButtons; + private Container rightAreaBackground = null!; + private Container rightAreaButtons = null!; - private Container mainContent; - private BeatmapCardContentBackground mainContentBackground; + private Container mainContent = null!; + private BeatmapCardContentBackground mainContentBackground = null!; + private FillFlowContainer statisticsContainer = null!; - private GridContainer titleContainer; - private GridContainer artistContainer; - private FillFlowContainer statisticsContainer; - - private FillFlowContainer idleBottomContent; - private BeatmapCardDownloadProgressBar downloadProgressBar; + private FillFlowContainer idleBottomContent = null!; + private BeatmapCardDownloadProgressBar downloadProgressBar = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; public BeatmapCard(APIBeatmapSet beatmapSet) : base(HoverSampleSet.Submit) @@ -71,14 +69,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards downloadTracker = new BeatmapDownloadTracker(beatmapSet); } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay? beatmapSetOverlay) { Width = width; Height = height; CornerRadius = corner_radius; Masking = true; + FillFlowContainer leftIconArea; + GridContainer titleContainer; + GridContainer artistContainer; + InternalChildren = new Drawable[] { downloadTracker, @@ -335,6 +337,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards Margin = new MarginPadding { Left = 5 } }; } + + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID); } protected override void LoadComplete() From 0f9ebe3d5d9b0c9a4e213b5c9ea12fbb4ae9e817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 16:55:05 +0200 Subject: [PATCH 41/52] Use beatmap cards in beatmap listing overlay --- .../BeatmapListing/BeatmapListingFilterControl.cs | 2 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index fa57191ef3..38f2bdb34f 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -119,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - sortControlBackground.Colour = colourProvider.Background5; + sortControlBackground.Colour = colourProvider.Background4; } public void Search(string query) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index e08af52a72..49f2f5c211 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,12 +15,11 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing; -using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -34,7 +33,7 @@ namespace osu.Game.Overlays private Drawable currentContent; private Container panelTarget; - private FillFlowContainer foundContent; + private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; private SupporterRequiredDrawable supporterRequiredContent; private BeatmapListingFilterControl filterControl; @@ -69,7 +68,7 @@ namespace osu.Game.Overlays new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background4, + Colour = ColourProvider.Background5, }, panelTarget = new Container { @@ -79,7 +78,7 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { - foundContent = new FillFlowContainer(), + foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), supporterRequiredContent = new SupporterRequiredDrawable(), } @@ -136,7 +135,7 @@ namespace osu.Game.Overlays return; } - var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) + var newPanels = searchResult.Results.Select(b => new BeatmapCard(b) { Anchor = 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. - var content = new FillFlowContainer + var content = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 42b09fd1ece244ba589df58ec8fccbdd16cf5795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Oct 2021 19:20:57 +0200 Subject: [PATCH 42/52] Use beatmap cards in user profile overlay --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 36b94283e5..e46e503dfa 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -6,10 +6,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; 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); protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 - ? new GridBeatmapPanel(model) + ? new BeatmapCard(model) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From ec2265d5bbddc2690fcb028be0dc44d96ec7f051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Oct 2021 19:21:46 +0200 Subject: [PATCH 43/52] Use beatmap cards in spotlights layout --- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index b95b0a1afc..cc553ad361 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -13,9 +13,9 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; using System.Linq; using System.Threading; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.BeatmapListing.Panels; namespace osu.Game.Overlays.Rankings { @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(10), - Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b) + Children = response.BeatmapSets.Select(b => new BeatmapCard(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From a188d6662fa72452f82b94815be841b5be558f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Oct 2021 19:44:32 +0200 Subject: [PATCH 44/52] Use beatmap card in solo spectator screen --- osu.Game/Screens/Play/SoloSpectator.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 5ecdb91718..45601999a0 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Configuration; using osu.Game.Graphics; 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.Responses; using osu.Game.Online.Spectator; -using osu.Game.Overlays.BeatmapListing.Panels; +using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -52,6 +53,9 @@ namespace osu.Game.Screens.Play [Resolved] private BeatmapModelDownloader beatmapDownloader { get; set; } + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + private Container beatmapPanelContainer; private TriangleButton watchButton; private SettingsCheckbox automaticDownload; @@ -73,7 +77,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config) + private void load(OsuConfigManager config) { InternalChild = new Container { @@ -88,7 +92,7 @@ namespace osu.Game.Screens.Play { new Box { - Colour = colours.GreySeafoamDark, + Colour = colourProvider.Background5, RelativeSizeAxes = Axes.Both, }, new FillFlowContainer @@ -229,7 +233,7 @@ namespace osu.Game.Screens.Play onlineBeatmapRequest.Success += beatmapSet => Schedule(() => { this.beatmapSet = beatmapSet; - beatmapPanelContainer.Child = new GridBeatmapPanel(this.beatmapSet); + beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet); checkForAutomaticDownload(); }); From 1d96542a2ac430d75e3d6177d3f503775e13577d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Nov 2021 12:19:24 +0900 Subject: [PATCH 45/52] Fix incorrect `ConfigureAwait` specification causing stalled test This only occurs on upcoming changes I have (occurred when switching existing skin import tests across to realm). Unsure why it was set to `true`, seems like a weird oversight. --- osu.Game/Stores/RealmArchiveModelImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 6370d4ebe4..b74670e722 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -253,7 +253,7 @@ namespace osu.Game.Stores 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(); - return await scheduledImport.ConfigureAwait(true); + return await scheduledImport.ConfigureAwait(false); } /// From 13612c0d02b0b1d94d3b8b293c025757a452cf01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Nov 2021 14:38:39 +0900 Subject: [PATCH 46/52] Add equality support to `ILive` types --- osu.Game.Tests/Database/RealmLiveTests.cs | 9 ++++----- osu.Game/Database/EntityFrameworkLive.cs | 4 ++++ osu.Game/Database/ILive.cs | 3 ++- osu.Game/Database/IPostImports.cs | 2 +- osu.Game/Database/RealmLive.cs | 2 ++ 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 33aa1afb89..41a81382f8 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Models; using Realms; @@ -18,15 +17,15 @@ namespace osu.Game.Tests.Database public class RealmLiveTests : RealmTest { [Test] - public void TestLiveCastability() + public void TestLiveEquality() { RunTestWithRealm((realmFactory, _) => { - RealmLive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); + ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); - ILive iBeatmap = beatmap; + ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(); - Assert.AreEqual(0, iBeatmap.Value.Length); + Assert.AreEqual(beatmap, beatmap2); }); } diff --git a/osu.Game/Database/EntityFrameworkLive.cs b/osu.Game/Database/EntityFrameworkLive.cs index 1d7b53911a..0fe8fea1ff 100644 --- a/osu.Game/Database/EntityFrameworkLive.cs +++ b/osu.Game/Database/EntityFrameworkLive.cs @@ -3,6 +3,8 @@ using System; +#nullable enable + namespace osu.Game.Database { public class EntityFrameworkLive : ILive where T : class @@ -30,5 +32,7 @@ namespace osu.Game.Database } public T Value { get; } + + public bool Equals(ILive? other) => ID == other?.ID; } } diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/ILive.cs index 9359b09eaf..ed2b926782 100644 --- a/osu.Game/Database/ILive.cs +++ b/osu.Game/Database/ILive.cs @@ -9,7 +9,8 @@ namespace osu.Game.Database /// A wrapper to provide access to database backed classes in a thread-safe manner. /// /// The databased type. - public interface ILive where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more. + public interface ILive : IEquatable> + where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more. { Guid ID { get; } diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs index b3b83f23ef..adb3a7108d 100644 --- a/osu.Game/Database/IPostImports.cs +++ b/osu.Game/Database/IPostImports.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace osu.Game.Database { - public interface IPostImports + public interface IPostImports where TModel : class { /// diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index abb69644d6..d988a81739 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -107,5 +107,7 @@ namespace osu.Game.Database // 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 => (fetchedContext != null && SynchronizationContext.Current == fetchedContext) || fetchedThreadId == Thread.CurrentThread.ManagedThreadId; + + public bool Equals(ILive? other) => ID == other?.ID; } } From 40d1b97af10fa5d5a6f8a70f4d65886da78181cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Nov 2021 14:39:35 +0900 Subject: [PATCH 47/52] Avoid attempting to fetch a non-managed `RealmLive` instance from the realm backing For compatibility reasons, we quite often convert completely unmanaged instances to `ILive`s so they fit the required parameters of a property or method call. This ensures such cases will not cause any issues when trying to interact with the underlying data. Originally I had this allowing write operations, but that seems a bit unsafe (when performing a write one would assume that the underlying data is being persisted, whereas in this case it is not). We can change this if the requirements change in the future, but I think throwing is the safest bet for now. --- osu.Game.Tests/Database/RealmLiveTests.cs | 17 +++++++++++++++++ osu.Game/Database/EntityFrameworkLive.cs | 3 +++ osu.Game/Database/ILive.cs | 5 +++++ osu.Game/Database/RealmLive.cs | 20 ++++++++++++++++---- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 33aa1afb89..4f7681e3ae 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -30,6 +30,23 @@ namespace osu.Game.Tests.Database }); } + [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(() => liveBeatmap.PerformWrite(l => l.Hidden = true)); + + Assert.IsFalse(beatmap.Hidden); + Assert.IsFalse(liveBeatmap.Value.Hidden); + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + } + [Test] public void TestValueAccessWithOpenContext() { diff --git a/osu.Game/Database/EntityFrameworkLive.cs b/osu.Game/Database/EntityFrameworkLive.cs index 1d7b53911a..e4507b8d3b 100644 --- a/osu.Game/Database/EntityFrameworkLive.cs +++ b/osu.Game/Database/EntityFrameworkLive.cs @@ -9,6 +9,7 @@ namespace osu.Game.Database { public EntityFrameworkLive(T item) { + IsManaged = true; // no way to really know. Value = item; } @@ -29,6 +30,8 @@ namespace osu.Game.Database perform(Value); } + public bool IsManaged { get; } + public T Value { get; } } } diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/ILive.cs index 9359b09eaf..ae287f1f6f 100644 --- a/osu.Game/Database/ILive.cs +++ b/osu.Game/Database/ILive.cs @@ -31,6 +31,11 @@ namespace osu.Game.Database /// The action to perform. void PerformWrite(Action perform); + /// + /// Whether this instance is tracking data which is managed by the database backing. + /// + bool IsManaged { get; } + /// /// Resolve the value of this instance on the current thread's context. /// diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index abb69644d6..fe631a9964 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -17,6 +17,8 @@ namespace osu.Game.Database { public Guid ID { get; } + public bool IsManaged { get; } + private readonly SynchronizationContext? fetchedContext; private readonly int fetchedThreadId; @@ -33,8 +35,13 @@ namespace osu.Game.Database { this.data = data; - fetchedContext = SynchronizationContext.Current; - fetchedThreadId = Thread.CurrentThread.ManagedThreadId; + if (data.IsManaged) + { + IsManaged = true; + + fetchedContext = SynchronizationContext.Current; + fetchedThreadId = Thread.CurrentThread.ManagedThreadId; + } ID = data.ID; } @@ -75,13 +82,18 @@ namespace osu.Game.Database /// Perform a write operation on this live object. /// /// The action to perform. - public void PerformWrite(Action perform) => + public void PerformWrite(Action perform) + { + if (!IsManaged) + throw new InvalidOperationException("Can't perform writes on a non-managed underlying value"); + PerformRead(t => { var transaction = t.Realm.BeginWrite(); perform(t); transaction.Commit(); }); + } public T Value { @@ -102,7 +114,7 @@ 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) private bool isCorrectThread From 5de2f6211d72a7d1820e223faa6d076ad7349f8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Nov 2021 15:32:33 +0900 Subject: [PATCH 48/52] Show a spinner instead of the download button on the new card during beatmap download --- .../Cards/Buttons/BeatmapCardIconButton.cs | 6 +- .../Drawables/Cards/Buttons/DownloadButton.cs | 56 ++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index ad9caf7e34..e362e3abeb 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -54,6 +54,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons protected readonly SpriteIcon Icon; + protected override Container Content => content; + private readonly Container content; protected BeatmapCardIconButton() @@ -61,7 +63,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Origin = Anchor.Centre; Anchor = Anchor.Centre; - Child = content = new Container + base.Content.Add(content = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -75,7 +77,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Anchor = Anchor.Centre } } - }; + }); Size = new Vector2(24); IconSize = 12; diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 1a514c8d36..150a5b1d15 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -3,14 +3,17 @@ #nullable enable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Resources.Localisation.Web; +using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { @@ -23,6 +26,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private Bindable preferNoVideo = null!; + private readonly LoadingSpinner spinner; + [Resolved] private BeatmapModelDownloader beatmaps { get; set; } = null!; @@ -30,6 +35,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { Icon.Icon = FontAwesome.Solid.Download; + Content.Add(spinner = new LoadingSpinner { Size = new Vector2(IconSize) }); + this.beatmapSet = beatmapSet; } @@ -49,21 +56,44 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void updateState() { - this.FadeTo(state.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - if (beatmapSet.Availability.DownloadDisabled) + switch (state.Value) { - Enabled.Value = false; - TooltipText = BeatmapsetsStrings.AvailabilityDisabled; - return; + case DownloadState.Downloading: + case DownloadState.Importing: + Action = null; + TooltipText = string.Empty; + spinner.Show(); + Icon.Hide(); + return; + + case DownloadState.LocallyAvailable: + Action = null; + TooltipText = string.Empty; + this.FadeOut(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + break; + + case DownloadState.NotDownloaded: + if (beatmapSet.Availability.DownloadDisabled) + { + Enabled.Value = false; + TooltipText = BeatmapsetsStrings.AvailabilityDisabled; + return; + } + + Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); + this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + spinner.Hide(); + Icon.Show(); + + if (!beatmapSet.HasVideo) + TooltipText = BeatmapsetsStrings.PanelDownloadAll; + else + TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; + break; + + default: + throw new InvalidOperationException($"Unknown {nameof(DownloadState)} specified."); } - - if (!beatmapSet.HasVideo) - TooltipText = BeatmapsetsStrings.PanelDownloadAll; - else - TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; - - Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); } } } From bf443a5a7a447b05c2cbed666022ae78d688568c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Nov 2021 15:36:16 +0900 Subject: [PATCH 49/52] Switch unnecessary `return` to `break` instead --- osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 150a5b1d15..c94e335e8f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons TooltipText = string.Empty; spinner.Show(); Icon.Hide(); - return; + break; case DownloadState.LocallyAvailable: Action = null; From 8be2defd09d018bfe4df29373005b6badda0844a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 17:17:06 +0900 Subject: [PATCH 50/52] Right-align avatar --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 5b90713148..6c518616e2 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -194,30 +194,21 @@ namespace osu.Game.Screens.OnlinePlay RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { - new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) }, Content = new[] { new Drawable[] { - ownerAvatar = new OwnerAvatar - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(ICON_HEIGHT), - Margin = new MarginPadding { Left = 8, Right = 8, }, - Masking = true, - CornerRadius = 4, - }, difficultyIconContainer = new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 8 }, + Margin = new MarginPadding { Left = 8, Right = 8 }, }, new FillFlowContainer { @@ -280,7 +271,7 @@ namespace osu.Game.Screens.OnlinePlay Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = 8, Right = 10, }, + Margin = new MarginPadding { Left = 8 }, AutoSizeAxes = Axes.Both, Spacing = new Vector2(5), ChildrenEnumerable = CreateButtons().Select(button => button.With(b => @@ -288,7 +279,16 @@ namespace osu.Game.Screens.OnlinePlay b.Anchor = Anchor.Centre; b.Origin = Anchor.Centre; })) - } + }, + ownerAvatar = new OwnerAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(ICON_HEIGHT), + Margin = new MarginPadding { Left = 8, Right = 8, }, + Masking = true, + CornerRadius = 4, + }, } } }, From e9a19aacd7c1551dbcfb57573ec86cda82109c28 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 17:23:50 +0900 Subject: [PATCH 51/52] Fix tests by requiring host --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 8 ++++---- .../Visual/Multiplayer/TestMultiplayerRoomManager.cs | 4 +++- .../Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 027769995a..4521a7fa0f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } - }); + }, API.LocalUser.Value); }); AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter()); @@ -283,7 +283,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } - }); + }, API.LocalUser.Value); }); AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter()); @@ -336,7 +336,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } - }); + }, API.LocalUser.Value); }); AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter()); @@ -597,7 +597,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } - }); + }, API.LocalUser.Value); }); AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter()); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index f8419b4164..a1f010f082 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; 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 is fired. /// /// The room. - public void AddServerSideRoom(Room room) => requestsHandler.AddServerSideRoom(room); + /// The host. + public void AddServerSideRoom(Room room, APIUser host) => requestsHandler.AddServerSideRoom(room, host); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 0b7d9af5e7..abcf31c007 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value); apiRoom.Password.Value = createRoomRequest.Room.Password.Value; - AddServerSideRoom(apiRoom); + AddServerSideRoom(apiRoom, localUser); var responseRoom = new APICreatedRoom(); responseRoom.CopyFrom(createResponseRoom(apiRoom, false)); @@ -125,9 +125,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// Adds a room to a local "server-side" list that's returned when a is fired. /// /// The room. - public void AddServerSideRoom(Room room) + /// The room host. + public void AddServerSideRoom(Room room, APIUser host) { room.RoomID.Value ??= currentRoomId++; + room.Host.Value = host; for (int i = 0; i < room.Playlist.Count; i++) { From e1445dcb058495d909efab2800f84d51e0f81aaf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 26 Nov 2021 17:40:45 +0900 Subject: [PATCH 52/52] Only show owner in match subscreen --- .../TestSceneDrawableRoomPlaylist.cs | 18 ++++++++++++++---- .../Screens/OnlinePlay/DrawableRoomPlaylist.cs | 6 ++++-- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 16 ++++++++++++---- .../DrawableRoomPlaylistWithResults.cs | 13 ++++++++----- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index b0174104f7..13d98145a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Beatmaps; +using osu.Game.Users.Drawables; using osuTK; using osuTK.Input; @@ -308,6 +309,15 @@ namespace osu.Game.Tests.Visual.Multiplayer 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().All(a => a.IsPresent == withOwner)); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -331,11 +341,11 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().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", () => { - Child = playlist = new TestPlaylist(allowEdit, allowSelection) + Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -415,8 +425,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public new IReadOnlyDictionary> ItemMap => base.ItemMap; - public TestPlaylist(bool allowEdit, bool allowSelection) - : base(allowEdit, allowSelection) + public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + : base(allowEdit, allowSelection, showItemOwner: showItemOwner) { } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index f5522cd25d..6deca0482a 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -20,11 +20,13 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool allowEdit; 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.allowSelection = allowSelection; + this.showItemOwner = showItemOwner; ((ReversibleFillFlowContainer)ListContainer).Reverse = reverse; } @@ -56,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection) + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection, showItemOwner) { SelectedItem = { BindTarget = SelectedItem }, RequestDeletion = requestDeletion diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 6c518616e2..6cbdc80d60 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -69,10 +69,11 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool allowEdit; private readonly bool allowSelection; + private readonly bool showItemOwner; 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) { Item = item; @@ -80,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay // TODO: edit support should be moved out into a derived class this.allowEdit = allowEdit; this.allowSelection = allowSelection; + this.showItemOwner = showItemOwner; beatmap.BindTo(item.Beatmap); valid.BindTo(item.Valid); @@ -142,7 +144,12 @@ namespace osu.Game.Screens.OnlinePlay maskingContainer.BorderColour = colours.Red; } - userLookupCache.GetUserAsync(Item.OwnerID).ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + 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; @@ -271,7 +278,7 @@ namespace osu.Game.Screens.OnlinePlay Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = 8 }, + Margin = new MarginPadding { Horizontal = 8 }, AutoSizeAxes = Axes.Both, Spacing = new Vector2(5), ChildrenEnumerable = CreateButtons().Select(button => button.With(b => @@ -285,9 +292,10 @@ namespace osu.Game.Screens.OnlinePlay Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(ICON_HEIGHT), - Margin = new MarginPadding { Left = 8, Right = 8, }, + Margin = new MarginPadding { Right = 8 }, Masking = true, CornerRadius = 4, + Alpha = showItemOwner ? 1 : 0 }, } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs index 575f336e58..8b1bb7abc1 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs @@ -19,13 +19,16 @@ namespace osu.Game.Screens.OnlinePlay { public Action RequestShowResults; - public DrawableRoomPlaylistWithResults() - : base(false, true) + private readonly bool showItemOwner; + + public DrawableRoomPlaylistWithResults(bool showItemOwner = false) + : base(false, true, showItemOwner: showItemOwner) { + this.showItemOwner = showItemOwner; } protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => - new DrawableRoomPlaylistItemWithResults(item, false, true) + new DrawableRoomPlaylistItemWithResults(item, false, true, showItemOwner) { RequestShowResults = () => RequestShowResults(item), SelectedItem = { BindTarget = SelectedItem }, @@ -35,8 +38,8 @@ namespace osu.Game.Screens.OnlinePlay { public Action RequestShowResults; - public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection) - : base(item, allowEdit, allowSelection) + public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) + : base(item, allowEdit, allowSelection, showItemOwner) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 1e3cfdbcbb..077e9cef93 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -153,7 +153,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer null, new Drawable[] { - playlist = new DrawableRoomPlaylist(false, false, true) + playlist = new DrawableRoomPlaylist(false, false, true, true) { RelativeSizeAxes = Axes.Both, },