From cdcad94e9f0a8ce75c6be8572408795aaa6bde16 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 17:47:29 +0900 Subject: [PATCH 1/8] Handle exception thrown due to custom stoage on startup --- osu.Game/IO/OsuStorage.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index f5ce1c0105..8bcc0941c1 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -34,7 +34,17 @@ namespace osu.Game.IO var customStoragePath = storageConfig.Get(StorageConfig.FullPath); if (!string.IsNullOrEmpty(customStoragePath)) - ChangeTargetStorage(host.GetStorage(customStoragePath)); + { + try + { + ChangeTargetStorage(host.GetStorage(customStoragePath)); + } + catch (Exception ex) + { + Logger.Log($"Couldn't use custom storage path ({customStoragePath}): {ex}. Using default path.", LoggingTarget.Runtime, LogLevel.Error); + ChangeTargetStorage(defaultStorage); + } + } } protected override void ChangeTargetStorage(Storage newStorage) From dbbee481f60b21911b5c67fa1d575272ac7a3a25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:01:45 +0900 Subject: [PATCH 2/8] Expose dialog body text getter --- osu.Game/Overlays/Dialog/PopupDialog.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 02ef900dc5..1bcbe4dd2f 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -42,25 +42,34 @@ namespace osu.Game.Overlays.Dialog set => icon.Icon = value; } - private string text; + private string headerText; public string HeaderText { - get => text; + get => headerText; set { - if (text == value) + if (headerText == value) return; - text = value; - + headerText = value; header.Text = value; } } + private string bodyText; + public string BodyText { - set => body.Text = value; + get => bodyText; + set + { + if (bodyText == value) + return; + + bodyText = value; + body.Text = value; + } } public IEnumerable Buttons From 1effe71ec2279ea53aafe07e230160312612b5e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:03:09 +0900 Subject: [PATCH 3/8] Add dialog for storage options --- osu.Game/IO/OsuStorage.cs | 95 ++++++++++++++++++++++++++----- osu.Game/Screens/Menu/MainMenu.cs | 79 +++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 13 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8bcc0941c1..3d6903c56f 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -13,12 +15,30 @@ namespace osu.Game.IO { public class OsuStorage : WrappedStorage { + /// + /// Indicates the error (if any) that occurred when initialising the custom storage during initial startup. + /// + public readonly OsuStorageError Error; + + /// + /// The custom storage path as selected by the user. + /// + [CanBeNull] + public string CustomStoragePath => storageConfig.Get(StorageConfig.FullPath); + + /// + /// The default storage path to be used if a custom storage path hasn't been selected or is not accessible. + /// + [NotNull] + public string DefaultStoragePath => defaultStorage.GetFullPath("."); + private readonly GameHost host; private readonly StorageConfigManager storageConfig; + private readonly Storage defaultStorage; - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; + public static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - internal static readonly string[] IGNORE_FILES = + public static readonly string[] IGNORE_FILES = { "framework.ini", "storage.ini" @@ -28,23 +48,53 @@ namespace osu.Game.IO : base(defaultStorage, string.Empty) { this.host = host; + this.defaultStorage = defaultStorage; storageConfig = new StorageConfigManager(defaultStorage); - var customStoragePath = storageConfig.Get(StorageConfig.FullPath); + if (!string.IsNullOrEmpty(CustomStoragePath)) + TryChangeToCustomStorage(out Error); + } - if (!string.IsNullOrEmpty(customStoragePath)) + /// + /// Resets the custom storage path, changing the target storage to the default location. + /// + public void ResetCustomStoragePath() + { + storageConfig.Set(StorageConfig.FullPath, string.Empty); + storageConfig.Save(); + + ChangeTargetStorage(defaultStorage); + } + + /// + /// Attempts to change to the user's custom storage path. + /// + /// The error that occurred. + /// Whether the custom storage path was used successfully. If not, will be populated with the reason. + public bool TryChangeToCustomStorage(out OsuStorageError error) + { + Debug.Assert(!string.IsNullOrEmpty(CustomStoragePath)); + + error = OsuStorageError.None; + Storage lastStorage = UnderlyingStorage; + + try { - try - { - ChangeTargetStorage(host.GetStorage(customStoragePath)); - } - catch (Exception ex) - { - Logger.Log($"Couldn't use custom storage path ({customStoragePath}): {ex}. Using default path.", LoggingTarget.Runtime, LogLevel.Error); - ChangeTargetStorage(defaultStorage); - } + Storage userStorage = host.GetStorage(CustomStoragePath); + + if (!userStorage.GetFiles(".").Any()) + error = OsuStorageError.AccessibleButEmpty; + + ChangeTargetStorage(userStorage); } + catch + { + error = OsuStorageError.NotAccessible; + ChangeTargetStorage(lastStorage); + } + + return error == OsuStorageError.None; } protected override void ChangeTargetStorage(Storage newStorage) @@ -155,4 +205,23 @@ namespace osu.Game.IO } } } + + public enum OsuStorageError + { + /// + /// No error. + /// + None, + + /// + /// Occurs when the target storage directory is accessible but does not already contain game files. + /// Only happens when the user changes the storage directory and then moves the files manually or mounts a different device to the same path. + /// + AccessibleButEmpty, + + /// + /// Occurs when the target storage directory cannot be accessed at all. + /// + NotAccessible, + } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 9245df2a7d..c391742c45 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; @@ -15,6 +16,7 @@ using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -171,6 +173,9 @@ namespace osu.Game.Screens.Menu return s; } + [Resolved] + private Storage storage { get; set; } + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -187,6 +192,9 @@ namespace osu.Game.Screens.Menu Track.Start(); } } + + if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) + dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); } private bool exitConfirmed; @@ -308,5 +316,76 @@ namespace osu.Game.Screens.Menu }; } } + + private class StorageErrorDialog : PopupDialog + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private OsuGameBase osuGame { get; set; } + + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) + { + HeaderText = "osu! storage error"; + Icon = FontAwesome.Solid.ExclamationTriangle; + + var buttons = new List(); + + BodyText = $"osu! encountered an error when trying to use the custom storage path ('{storage.CustomStoragePath}').\n\n"; + + switch (error) + { + case OsuStorageError.NotAccessible: + BodyText += $"The default storage path ('{storage.DefaultStoragePath}') is currently being used because the custom storage path is not accessible.\n\n" + + "Is it on a removable device that is not currently connected?"; + + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Try again", + Action = () => + { + if (!storage.TryChangeToCustomStorage(out var nextError)) + dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); + } + }, + new PopupDialogOkButton + { + Text = "Use the default path from now on", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Only use the default path for this session", + }, + }); + break; + + case OsuStorageError.AccessibleButEmpty: + BodyText += "The custom storage path is currently being used but is empty.\n\n" + + "Have you moved the files elsewhere?"; + + // Todo: Provide the option to search for the files similar to migration. + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Reset to default", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Keep using the custom path" + } + }); + + break; + } + + Buttons = buttons; + } + } } } From 8f792603ee6b03d19bba1ffc7d74203904205a35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jul 2020 22:40:45 +0900 Subject: [PATCH 4/8] Apply suggestions from code review Co-authored-by: Dean Herbert --- osu.Game/Screens/Menu/MainMenu.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c391742c45..d64d9b69fe 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -332,13 +332,11 @@ namespace osu.Game.Screens.Menu var buttons = new List(); - BodyText = $"osu! encountered an error when trying to use the custom storage path ('{storage.CustomStoragePath}').\n\n"; switch (error) { case OsuStorageError.NotAccessible: - BodyText += $"The default storage path ('{storage.DefaultStoragePath}') is currently being used because the custom storage path is not accessible.\n\n" - + "Is it on a removable device that is not currently connected?"; + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; buttons.AddRange(new PopupDialogButton[] { @@ -353,31 +351,30 @@ namespace osu.Game.Screens.Menu }, new PopupDialogOkButton { - Text = "Use the default path from now on", + Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, new PopupDialogCancelButton { - Text = "Only use the default path for this session", + Text = "Use default location for this session", }, }); break; case OsuStorageError.AccessibleButEmpty: - BodyText += "The custom storage path is currently being used but is empty.\n\n" - + "Have you moved the files elsewhere?"; + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { new PopupDialogOkButton { - Text = "Reset to default", + Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, new PopupDialogCancelButton { - Text = "Keep using the custom path" + Text = "Start fresh at specified location" } }); From ddac511c8c5c3b4ef641c1a80e4e9dbbe6359ce4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:41:51 +0900 Subject: [PATCH 5/8] Move start fresh button above --- osu.Game/Screens/Menu/MainMenu.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d64d9b69fe..dcb141cce5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -332,7 +332,6 @@ namespace osu.Game.Screens.Menu var buttons = new List(); - switch (error) { case OsuStorageError.NotAccessible: @@ -367,15 +366,15 @@ namespace osu.Game.Screens.Menu // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { + new PopupDialogCancelButton + { + Text = "Start fresh at specified location" + }, new PopupDialogOkButton { Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, - new PopupDialogCancelButton - { - Text = "Start fresh at specified location" - } }); break; From 00a2fbce06ac67d5bc077f10e43c302c98af629e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:41:58 +0900 Subject: [PATCH 6/8] Fix test failures --- osu.Game/IO/OsuStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 3d6903c56f..1d15294666 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -83,7 +83,7 @@ namespace osu.Game.IO { Storage userStorage = host.GetStorage(CustomStoragePath); - if (!userStorage.GetFiles(".").Any()) + if (!userStorage.ExistsDirectory(".") || !userStorage.GetFiles(".").Any()) error = OsuStorageError.AccessibleButEmpty; ChangeTargetStorage(userStorage); From a650a5ec83ca93d17709c9c34fb5922857e23d90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 23:44:26 +0900 Subject: [PATCH 7/8] Move dialog classes to own file --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 34 ++++++++ osu.Game/Screens/Menu/MainMenu.cs | 96 --------------------- osu.Game/Screens/Menu/StorageErrorDialog.cs | 79 +++++++++++++++++ 3 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Screens/Menu/ConfirmExitDialog.cs create mode 100644 osu.Game/Screens/Menu/StorageErrorDialog.cs diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs new file mode 100644 index 0000000000..d120eb21a8 --- /dev/null +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.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 System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class ConfirmExitDialog : PopupDialog + { + public ConfirmExitDialog(Action confirm, Action cancel) + { + HeaderText = "Are you sure you want to exit?"; + BodyText = "Last chance to back out."; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Goodbye", + Action = confirm + }, + new PopupDialogCancelButton + { + Text = @"Just a little more", + Action = cancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcb141cce5..76950982e6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,8 +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.Linq; using osuTK; using osuTK.Graphics; @@ -10,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; @@ -19,7 +16,6 @@ using osu.Game.Graphics.Containers; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Multi; @@ -291,97 +287,5 @@ namespace osu.Game.Screens.Menu this.FadeOut(3000); return base.OnExiting(next); } - - private class ConfirmExitDialog : PopupDialog - { - public ConfirmExitDialog(Action confirm, Action cancel) - { - HeaderText = "Are you sure you want to exit?"; - BodyText = "Last chance to back out."; - - Icon = FontAwesome.Solid.ExclamationTriangle; - - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Goodbye", - Action = confirm - }, - new PopupDialogCancelButton - { - Text = @"Just a little more", - Action = cancel - }, - }; - } - } - - private class StorageErrorDialog : PopupDialog - { - [Resolved] - private DialogOverlay dialogOverlay { get; set; } - - [Resolved] - private OsuGameBase osuGame { get; set; } - - public StorageErrorDialog(OsuStorage storage, OsuStorageError error) - { - HeaderText = "osu! storage error"; - Icon = FontAwesome.Solid.ExclamationTriangle; - - var buttons = new List(); - - switch (error) - { - case OsuStorageError.NotAccessible: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; - - buttons.AddRange(new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = "Try again", - Action = () => - { - if (!storage.TryChangeToCustomStorage(out var nextError)) - dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); - } - }, - new PopupDialogOkButton - { - Text = "Reset to default location", - Action = storage.ResetCustomStoragePath - }, - new PopupDialogCancelButton - { - Text = "Use default location for this session", - }, - }); - break; - - case OsuStorageError.AccessibleButEmpty: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; - - // Todo: Provide the option to search for the files similar to migration. - buttons.AddRange(new PopupDialogButton[] - { - new PopupDialogCancelButton - { - Text = "Start fresh at specified location" - }, - new PopupDialogOkButton - { - Text = "Reset to default location", - Action = storage.ResetCustomStoragePath - }, - }); - - break; - } - - Buttons = buttons; - } - } } } diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs new file mode 100644 index 0000000000..38a6c07ce7 --- /dev/null +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -0,0 +1,79 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.IO; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class StorageErrorDialog : PopupDialog + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private OsuGameBase osuGame { get; set; } + + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) + { + HeaderText = "osu! storage error"; + Icon = FontAwesome.Solid.ExclamationTriangle; + + var buttons = new List(); + + switch (error) + { + case OsuStorageError.NotAccessible: + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; + + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Try again", + Action = () => + { + if (!storage.TryChangeToCustomStorage(out var nextError)) + dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); + } + }, + new PopupDialogOkButton + { + Text = "Reset to default location", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Use default location for this session", + }, + }); + break; + + case OsuStorageError.AccessibleButEmpty: + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; + + // Todo: Provide the option to search for the files similar to migration. + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = "Start fresh at specified location" + }, + new PopupDialogOkButton + { + Text = "Reset to default location", + Action = storage.ResetCustomStoragePath + }, + }); + + break; + } + + Buttons = buttons; + } + } +} From 3f3bfb1ffbe001ed4016b6750ff830c5f983279b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 23:51:16 +0900 Subject: [PATCH 8/8] Minor reshuffling / recolouring --- osu.Game/Screens/Menu/StorageErrorDialog.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs index 38a6c07ce7..dcaad4013a 100644 --- a/osu.Game/Screens/Menu/StorageErrorDialog.cs +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Menu buttons.AddRange(new PopupDialogButton[] { - new PopupDialogOkButton + new PopupDialogCancelButton { Text = "Try again", Action = () => @@ -41,15 +41,15 @@ namespace osu.Game.Screens.Menu dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); } }, + new PopupDialogCancelButton + { + Text = "Use default location until restart", + }, new PopupDialogOkButton { Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, - new PopupDialogCancelButton - { - Text = "Use default location for this session", - }, }); break;