From 6bb06e9d611aeb9255c28a5936f59c0cdc2559f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 May 2020 22:57:31 +0900 Subject: [PATCH 01/12] Expose CurrentDirectory bindable for consumption --- .../Graphics/UserInterfaceV2/DirectorySelector.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs index ee428c0047..6ea026ad3d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs @@ -28,11 +28,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 private GameHost host { get; set; } [Cached] - private readonly Bindable currentDirectory = new Bindable(); + public readonly Bindable CurrentDirectory = new Bindable(); public DirectorySelector(string initialPath = null) { - currentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + CurrentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); } [BackgroundDependencyLoader] @@ -68,7 +68,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, }; - currentDirectory.BindValueChanged(updateDisplay, true); + CurrentDirectory.BindValueChanged(updateDisplay, true); } private void updateDisplay(ValueChangedEvent directory) @@ -86,9 +86,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 } else { - directoryFlow.Add(new ParentDirectoryPiece(currentDirectory.Value.Parent)); + directoryFlow.Add(new ParentDirectoryPiece(CurrentDirectory.Value.Parent)); - foreach (var dir in currentDirectory.Value.GetDirectories().OrderBy(d => d.Name)) + foreach (var dir in CurrentDirectory.Value.GetDirectories().OrderBy(d => d.Name)) { if ((dir.Attributes & FileAttributes.Hidden) == 0) directoryFlow.Add(new DirectoryPiece(dir)); @@ -97,8 +97,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } catch (Exception) { - currentDirectory.Value = directory.OldValue; - + CurrentDirectory.Value = directory.OldValue; this.FlashColour(Color4.Red, 300); } } From cb0b25ac55c7db5e6b1cc4a941971676690388ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 May 2020 22:57:41 +0900 Subject: [PATCH 02/12] Throw better exceptions from OsuStorage --- osu.Game/IO/OsuStorage.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 71b01ce479..8109631ef9 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -48,11 +48,14 @@ namespace osu.Game.IO var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newLocation); + if (source.FullName == destination.FullName) + throw new ArgumentException("Destination provided is already the current location", nameof(newLocation)); + // ensure the new location has no files present, else hard abort if (destination.Exists) { if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) - throw new InvalidOperationException("Migration destination already has files present"); + throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); deleteRecursive(destination); } From 0b73063a89ba56edc8fe0d364fe105375fd63c3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 May 2020 22:58:05 +0900 Subject: [PATCH 03/12] Add basic (working) migration UI --- .../Sections/Maintenance/GeneralSettings.cs | 9 +- .../Maintenance/MigrationRunScreen.cs | 88 +++++++++++++++++++ .../Maintenance/MigrationSelectScreen.cs | 57 ++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 832673703b..8bdeadae5c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; @@ -26,8 +27,14 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay, OsuGame game) { + Add(importBeatmapsButton = new SettingsButton + { + Text = "Migrate storage to new location", + Action = () => game.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) + }); + if (beatmaps.SupportsImportFromStable) { Add(importBeatmapsButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs new file mode 100644 index 0000000000..76f01dc4b9 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -0,0 +1,88 @@ +// 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.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class MigrationRunScreen : OsuScreen + { + private readonly DirectoryInfo destination; + + [Resolved] + private OsuGame game { get; set; } + + public override bool AllowBackButton => false; + + public override bool AllowExternalScreenChange => false; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + private Task migrationTask; + + public MigrationRunScreen(DirectoryInfo destination) + { + this.destination = destination; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Migration in progress", + Font = OsuFont.Default.With(size: 48) + }, + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible } + } + } + }, + }; + + Beatmap.Value = Beatmap.Default; + + migrationTask = Task.Run(() => game.Migrate(destination.FullName)) + .ContinueWith(t => + { + if (t.IsFaulted) + Logger.Log($"Error during migration: {t.Exception?.Message}", level: LogLevel.Error); + + Schedule(this.Exit); + }); + } + + public override bool OnExiting(IScreen next) + { + // block until migration is finished + if (migrationTask?.IsCompleted == false) + return true; + + return base.OnExiting(next); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs new file mode 100644 index 0000000000..d1c2f6d6ee --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -0,0 +1,57 @@ +// 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.Containers; +using osu.Framework.Screens; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class MigrationSelectScreen : OsuScreen + { + private DirectorySelector directorySelector; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.8f), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { directorySelector = new DirectorySelector { RelativeSizeAxes = Axes.Both } }, + new Drawable[] + { + new OsuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Start", + Action = start + }, + } + } + }; + } + + private void start() + { + var target = directorySelector.CurrentDirectory.Value; + if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0) + target = target.CreateSubdirectory("osu-lazer"); + + ValidForResume = false; + this.Push(new MigrationRunScreen(target)); + } + } +} From 06f507496a0d4bb8865d226ea9af6d220a5ab4fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 May 2020 23:02:28 +0900 Subject: [PATCH 04/12] Delete migration source if no files exist after completion --- osu.Game/IO/OsuStorage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8109631ef9..443f4fdb69 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -87,6 +87,9 @@ namespace osu.Game.IO dir.Delete(true); } + + if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) + target.Delete(); } private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) From d04079f6ab7911b5d7f8fd633019993137b0fc65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 17:40:30 +0900 Subject: [PATCH 05/12] Fix directory selector not masking properly --- .../UserInterfaceV2/DirectorySelector.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs index 6ea026ad3d..ae34281bfb 100644 --- a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs @@ -40,19 +40,25 @@ namespace osu.Game.Graphics.UserInterfaceV2 { Padding = new MarginPadding(10); - InternalChildren = new Drawable[] + InternalChild = new GridContainer { - new FillFlowContainer + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { new CurrentDirectoryDisplay { - RelativeSizeAxes = Axes.X, - Height = 50, + RelativeSizeAxes = Axes.Both, }, + }, + new Drawable[] + { new OsuScrollContainer { RelativeSizeAxes = Axes.Both, @@ -65,7 +71,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } } - }, + } }; CurrentDirectory.BindValueChanged(updateDisplay, true); From 4e4a779d6827d76323702fad328c451c3e3fb000 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 17:40:43 +0900 Subject: [PATCH 06/12] Improve overall UI --- .../Sections/General/UpdateSettings.cs | 10 +- .../Sections/Maintenance/GeneralSettings.cs | 7 -- .../Maintenance/MigrationRunScreen.cs | 29 ++++- .../Maintenance/MigrationSelectScreen.cs | 105 +++++++++++++++--- 4 files changed, 123 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 188c9c05ef..b5d07ee7b1 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,7 +4,9 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Game.Configuration; +using osu.Game.Overlays.Settings.Sections.Maintenance; namespace osu.Game.Overlays.Settings.Sections.General { @@ -13,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.General protected override string Header => "Updates"; [BackgroundDependencyLoader] - private void load(Storage storage, OsuConfigManager config) + private void load(Storage storage, OsuConfigManager config, OsuGame game) { Add(new SettingsEnumDropdown { @@ -28,6 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.General Text = "Open osu! folder", Action = storage.OpenInNativeExplorer, }); + + Add(new SettingsButton + { + Text = "Change folder location...", + Action = () => game.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 8bdeadae5c..1dd079a8ab 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; @@ -29,12 +28,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay, OsuGame game) { - Add(importBeatmapsButton = new SettingsButton - { - Text = "Migrate storage to new location", - Action = () => game.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) - }); - if (beatmaps.SupportsImportFromStable) { Add(importBeatmapsButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index 76f01dc4b9..b29cd0d630 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens; +using osuTK; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -28,6 +29,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool HideOverlaysOnEnter => true; + private Task migrationTask; public MigrationRunScreen(DirectoryInfo destination) @@ -47,6 +50,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Direction = FillDirection.Vertical, Anchor = Anchor.Centre, Origin = Anchor.Centre, + Spacing = new Vector2(10), Children = new Drawable[] { new OsuSpriteText @@ -54,12 +58,26 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Migration in progress", - Font = OsuFont.Default.With(size: 48) + Font = OsuFont.Default.With(size: 40) + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "This could take a few minutes depending on the speed of your disk(s).", + Font = OsuFont.Default.With(size: 30) }, new LoadingSpinner(true) { State = { Value = Visibility.Visible } - } + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Please avoid interacting with the game!", + Font = OsuFont.Default.With(size: 30) + }, } }, }; @@ -76,6 +94,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }); } + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + this.FadeOut().Delay(250).Then().FadeIn(250); + } + public override bool OnExiting(IScreen next) { // block until migration is finished diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index d1c2f6d6ee..c1aa7f095c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -1,13 +1,21 @@ // 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.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Screens; +using osuTK; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -15,40 +23,101 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private DirectorySelector directorySelector; + public override bool AllowExternalScreenChange => false; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool HideOverlaysOnEnter => true; + [BackgroundDependencyLoader] - private void load() + private void load(OsuGame game, Storage storage, OsuColour colours) { - InternalChild = new GridContainer + game.Toolbar.Hide(); + + // begin selection in the parent directory of the current storage location + var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; + + InternalChild = new Container { + Masking = true, + CornerRadius = 10, RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f, 0.8f), + Children = new Drawable[] { - new Dimension(GridSizeMode.Relative, 0.8f), - new Dimension(), - }, - Content = new[] - { - new Drawable[] { directorySelector = new DirectorySelector { RelativeSizeAxes = Axes.Both } }, - new Drawable[] + new Box { - new OsuButton + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 300, - Text = "Start", - Action = start + new Dimension(), + new Dimension(GridSizeMode.Relative, 0.8f), + new Dimension(), }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Please select a new location", + Font = OsuFont.Default.With(size: 40) + }, + }, + new Drawable[] + { + directorySelector = new DirectorySelector(initialPath) + { + RelativeSizeAxes = Axes.Both, + } + }, + new Drawable[] + { + new TriangleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Begin folder migration", + Action = start + }, + } + } } } }; } + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + + this.FadeOut(250); + } + private void start() { var target = directorySelector.CurrentDirectory.Value; - if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0) - target = target.CreateSubdirectory("osu-lazer"); + + try + { + if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0) + target = target.CreateSubdirectory("osu-lazer"); + } + catch (Exception e) + { + Logger.Log($"Error during migration: {e?.Message}", level: LogLevel.Error); + return; + } ValidForResume = false; this.Push(new MigrationRunScreen(target)); From a5826116479b547084fa2d3d0fab0211ab83089a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 19:05:35 +0900 Subject: [PATCH 07/12] Add test coverage --- .../Settings/TestSceneMigrationScreens.cs | 36 +++++++++++++++++++ .../Maintenance/MigrationRunScreen.cs | 6 ++-- .../Maintenance/MigrationSelectScreen.cs | 10 +++--- 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs b/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs new file mode 100644 index 0000000000..2883e54385 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs @@ -0,0 +1,36 @@ +// 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.Threading; +using osu.Framework.Screens; +using osu.Game.Overlays.Settings.Sections.Maintenance; + +namespace osu.Game.Tests.Visual.Settings +{ + public class TestSceneMigrationScreens : ScreenTestScene + { + public TestSceneMigrationScreens() + { + AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen())); + } + + private class TestMigrationSelectScreen : MigrationSelectScreen + { + protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen()); + + private class TestMigrationRunScreen : MigrationRunScreen + { + protected override void PerformMigration() + { + Thread.Sleep(3000); + } + + public TestMigrationRunScreen() + : base(null) + { + } + } + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index b29cd0d630..b0b61554eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private readonly DirectoryInfo destination; - [Resolved] + [Resolved(canBeNull: true)] private OsuGame game { get; set; } public override bool AllowBackButton => false; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Beatmap.Value = Beatmap.Default; - migrationTask = Task.Run(() => game.Migrate(destination.FullName)) + migrationTask = Task.Run(PerformMigration) .ContinueWith(t => { if (t.IsFaulted) @@ -94,6 +94,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }); } + protected virtual void PerformMigration() => game?.Migrate(destination.FullName); + public override void OnEntering(IScreen last) { base.OnEntering(last); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index c1aa7f095c..79d842a617 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -29,10 +29,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public override bool HideOverlaysOnEnter => true; - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(OsuGame game, Storage storage, OsuColour colours) { - game.Toolbar.Hide(); + game?.Toolbar.Hide(); // begin selection in the parent directory of the current storage location var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; @@ -115,12 +115,14 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } catch (Exception e) { - Logger.Log($"Error during migration: {e?.Message}", level: LogLevel.Error); + Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error); return; } ValidForResume = false; - this.Push(new MigrationRunScreen(target)); + BeginMigration(target); } + + protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target)); } } From f3b1c32a85050ace4ad7f410913ea3665312e3de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 20:18:57 +0900 Subject: [PATCH 08/12] Update test logic for new exception type --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index ef2b20de64..7a20bd364b 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -211,7 +211,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); - Assert.Throws(() => osu.Migrate(customPath)); + Assert.Throws(() => osu.Migrate(customPath)); } finally { From 42f446faa9a8d19a0a49d970b8139e7bf28cf4c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 21:10:04 +0900 Subject: [PATCH 09/12] Fix remaining test failure --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index b5d07ee7b1..95a1868392 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { protected override string Header => "Updates"; - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuConfigManager config, OsuGame game) { Add(new SettingsEnumDropdown @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = "Change folder location...", - Action = () => game.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) + Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) }); } } From 155e918ca3063b4c03146144cc3b8f9a035d5566 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 21:40:26 +0900 Subject: [PATCH 10/12] Remove unused parameter --- .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 1dd079a8ab..832673703b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay, OsuGame game) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { From 364aa5aa129af4dbf996d4942b2f4884b6d53dbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 May 2020 22:42:42 +0900 Subject: [PATCH 11/12] Add protection against migrating to a nested folder --- .../NonVisual/CustomDataDirectoryTest.cs | 24 +++++++++++++++++++ osu.Game/IO/OsuStorage.cs | 3 +++ 2 files changed, 27 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 7a20bd364b..e8f052cdeb 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -220,6 +220,30 @@ namespace osu.Game.Tests.NonVisual } } + [Test] + public void TestMigrationToNestedTargetFails() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSameTargetFails))) + { + try + { + var osu = loadOsu(host); + + Assert.DoesNotThrow(() => osu.Migrate(customPath)); + + string subFolder = Path.Combine(customPath, "sub"); + + Directory.CreateDirectory(subFolder); + + Assert.Throws(() => osu.Migrate(subFolder)); + } + finally + { + host.Exit(); + } + } + } + private OsuGameBase loadOsu(GameHost host) { var osu = new OsuGameBase(); diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 443f4fdb69..e3888c0c28 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -51,6 +51,9 @@ namespace osu.Game.IO if (source.FullName == destination.FullName) throw new ArgumentException("Destination provided is already the current location", nameof(newLocation)); + if (destination.FullName.Contains(source.FullName)) + throw new ArgumentException("Destination provided is inside the source", nameof(newLocation)); + // ensure the new location has no files present, else hard abort if (destination.Exists) { From 6ca102bc3fb44d751986c3daa141825d8ed094ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 May 2020 13:19:03 +0900 Subject: [PATCH 12/12] Attempt delete operations more than once --- osu.Game/IO/OsuStorage.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 5393cbf7ae..499bcb4063 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -84,7 +84,7 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) continue; - fi.Delete(); + attemptOperation(() => fi.Delete()); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -92,11 +92,11 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) continue; - dir.Delete(true); + attemptOperation(() => dir.Delete(true)); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - target.Delete(); + attemptOperation(target.Delete); } private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) @@ -109,7 +109,7 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) continue; - attemptCopy(fi, Path.Combine(destination.FullName, fi.Name)); + attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); } foreach (DirectoryInfo dir in source.GetDirectories()) @@ -121,24 +121,27 @@ namespace osu.Game.IO } } - private static void attemptCopy(System.IO.FileInfo fileInfo, string destination) + /// + /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. + /// + /// The action to perform. + /// The number of attempts (250ms wait between each). + private static void attemptOperation(Action action, int attempts = 10) { - int tries = 5; - while (true) { try { - fileInfo.CopyTo(destination, true); + action(); return; } catch (Exception) { - if (tries-- == 0) + if (attempts-- == 0) throw; } - Thread.Sleep(50); + Thread.Sleep(250); } } }