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