diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 20738f859e..7e1641d16f 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework; @@ -54,6 +57,49 @@ namespace osu.Game.Database public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, gameHost as DesktopGameHost); + /// + /// Checks whether a valid location to run a stable import from can be determined starting from the supplied . + /// + /// The directory to check for stable import eligibility. + /// + /// If the return value is , + /// this parameter will contain the to use as the root directory for importing. + /// + public bool IsUsableForStableImport(DirectoryInfo? directory, [NotNullWhen(true)] out DirectoryInfo? stableRoot) + { + if (directory == null) + { + stableRoot = null; + return false; + } + + // A full stable installation will have a configuration file present. + // This is the best case scenario, as it may contain a custom beatmap directory we need to traverse to. + if (directory.GetFiles(@"osu!.*.cfg").Any()) + { + stableRoot = directory; + return true; + } + + // The user may only have their songs or skins folders left. + // We still want to allow them to import based on this. + if (directory.GetDirectories(@"Songs").Any() || directory.GetDirectories(@"Skins").Any()) + { + stableRoot = directory; + return true; + } + + // The user may have traversed *inside* their songs or skins folders. + if (directory.Parent != null && (directory.Name == @"Songs" || directory.Name == @"Skins")) + { + stableRoot = directory.Parent; + return true; + } + + stableRoot = null; + return false; + } + public bool CheckSongsFolderHardLinkAvailability() { var stableStorage = GetCurrentStableStorage(); diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 23f3b3e1af..24ac5e72e8 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -244,6 +244,8 @@ namespace osu.Game.Overlays.FirstRunSetup [Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes. private OsuGameBase? game { get; set; } + private bool changingDirectory; + protected override void LoadComplete() { base.LoadComplete(); @@ -259,24 +261,37 @@ namespace osu.Game.Overlays.FirstRunSetup private void onDirectorySelected(ValueChangedEvent directory) { - if (directory.NewValue == null) - { - Current.Value = string.Empty; + if (changingDirectory) return; + + try + { + changingDirectory = true; + + if (directory.NewValue == null) + { + Current.Value = string.Empty; + return; + } + + // DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this. + if (directory.OldValue?.FullName == directory.NewValue.FullName) + return; + + if (legacyImportManager.IsUsableForStableImport(directory.NewValue, out var stableRoot)) + { + this.HidePopover(); + + string path = stableRoot.FullName; + + legacyImportManager.UpdateStorage(path); + Current.Value = path; + currentDirectory.Value = stableRoot; + } } - - // DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this. - if (directory.OldValue?.FullName == directory.NewValue.FullName) - return; - - if (directory.NewValue?.GetFiles(@"osu!.*.cfg").Any() ?? false) + finally { - this.HidePopover(); - - string path = directory.NewValue.FullName; - - legacyImportManager.UpdateStorage(path); - Current.Value = path; + changingDirectory = false; } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 1b935b0cec..3f12b9c0df 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -1,11 +1,13 @@ // 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 System.Linq; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Framework.Screens; +using osu.Game.Database; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -13,9 +15,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private readonly TaskCompletionSource taskCompletionSource; + [Resolved] + private LegacyImportManager legacyImportManager { get; set; } = null!; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - protected override bool IsValidDirectory(DirectoryInfo? info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; + protected override bool IsValidDirectory(DirectoryInfo? info) => legacyImportManager.IsUsableForStableImport(info, out _); public override LocalisableString HeaderText => "Please select your osu!stable install location"; @@ -26,7 +31,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void OnSelection(DirectoryInfo directory) { - taskCompletionSource.TrySetResult(directory.FullName); + if (!legacyImportManager.IsUsableForStableImport(directory, out var stableRoot)) + throw new InvalidOperationException($@"{nameof(OnSelection)} was called on an invalid directory. This should never happen."); + + taskCompletionSource.TrySetResult(stableRoot.FullName); this.Exit(); }