diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index d1515acafa..5909b82c8f 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -18,6 +18,7 @@ using osu.Framework.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Updater;
using osu.Desktop.Windows;
+using osu.Game.IO;
namespace osu.Desktop
{
@@ -32,7 +33,7 @@ namespace osu.Desktop
noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false;
}
- public override Storage GetStorageForStableInstall()
+ public override StableStorage GetStorageForStableInstall()
{
try
{
@@ -40,7 +41,7 @@ namespace osu.Desktop
{
string stablePath = getStableInstallPath();
if (!string.IsNullOrEmpty(stablePath))
- return new DesktopStorage(stablePath, desktopHost);
+ return new StableStorage(stablePath, desktopHost);
}
}
catch (Exception)
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index b934ac556d..3c6a6ba302 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -64,7 +64,9 @@ namespace osu.Game.Beatmaps
protected override string[] HashableFileTypes => new[] { ".osu" };
- protected override string ImportFromStablePath => "Songs";
+ protected override string ImportFromStablePath => ".";
+
+ protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage();
private readonly RulesetStore rulesets;
private readonly BeatmapStore beatmaps;
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 9581edfc8d..232acba4a3 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -625,7 +625,7 @@ namespace osu.Game.Database
///
/// Set a storage with access to an osu-stable install for import purposes.
///
- public Func GetStableStorage { private get; set; }
+ public Func GetStableStorage { private get; set; }
///
/// Denotes whether an osu-stable installation is present to perform automated imports from.
@@ -638,9 +638,10 @@ namespace osu.Game.Database
protected virtual string ImportFromStablePath => null;
///
- /// Select paths to import from stable. Default implementation iterates all directories in .
+ /// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in .
///
- protected virtual IEnumerable GetStableImportPaths(Storage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath);
+ protected virtual IEnumerable GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath)
+ .Select(path => storage.GetFullPath(path));
///
/// Whether this specified path should be removed after successful import.
@@ -654,24 +655,33 @@ namespace osu.Game.Database
///
public Task ImportFromStableAsync()
{
- var stable = GetStableStorage?.Invoke();
+ var stableStorage = GetStableStorage?.Invoke();
- if (stable == null)
+ if (stableStorage == null)
{
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
return Task.CompletedTask;
}
- if (!stable.ExistsDirectory(ImportFromStablePath))
+ var storage = PrepareStableStorage(stableStorage);
+
+ if (!storage.ExistsDirectory(ImportFromStablePath))
{
// This handles situations like when the user does not have a Skins folder
Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
return Task.CompletedTask;
}
- return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray()));
+ return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray()));
}
+ ///
+ /// 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/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs
new file mode 100644
index 0000000000..d4b0d300ff
--- /dev/null
+++ b/osu.Game/IO/StableStorage.cs
@@ -0,0 +1,62 @@
+// 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 osu.Framework.Platform;
+
+namespace osu.Game.IO
+{
+ ///
+ /// A storage pointing to an osu-stable installation.
+ /// Provides methods for handling installations with a custom Song folder location.
+ ///
+ public class StableStorage : DesktopStorage
+ {
+ private const string stable_default_songs_path = "Songs";
+
+ private readonly DesktopGameHost host;
+ private readonly Lazy songsPath;
+
+ public StableStorage(string path, DesktopGameHost host)
+ : base(path, host)
+ {
+ this.host = host;
+
+ songsPath = new Lazy(locateSongsDirectory);
+ }
+
+ ///
+ /// Returns a pointing to the osu-stable Songs directory.
+ ///
+ public Storage GetSongStorage() => new DesktopStorage(songsPath.Value, host);
+
+ private string locateSongsDirectory()
+ {
+ var configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault();
+
+ if (configFile != null)
+ {
+ using (var stream = GetStream(configFile))
+ using (var textReader = new StreamReader(stream))
+ {
+ string line;
+
+ while ((line = textReader.ReadLine()) != null)
+ {
+ if (!line.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase)) continue;
+
+ var customDirectory = line.Split('=').LastOrDefault()?.Trim();
+ if (customDirectory != null && Path.IsPathFullyQualified(customDirectory))
+ return customDirectory;
+
+ break;
+ }
+ }
+ }
+
+ return GetFullPath(stable_default_songs_path);
+ }
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 0dc63dcd4b..15785ea6bd 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -28,7 +28,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
-using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Collections;
@@ -52,6 +51,7 @@ using osu.Game.Updater;
using osu.Game.Utils;
using LogLevel = osu.Framework.Logging.LogLevel;
using osu.Game.Database;
+using osu.Game.IO;
namespace osu.Game
{
@@ -88,7 +88,7 @@ namespace osu.Game
protected SentryLogger SentryLogger;
- public virtual Storage GetStorageForStableInstall() => null;
+ public virtual StableStorage GetStorageForStableInstall() => null;
public float ToolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0);
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index cf1d123c06..a6beb19876 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -71,8 +71,9 @@ namespace osu.Game.Scoring
}
}
- protected override IEnumerable GetStableImportPaths(Storage stableStorage)
- => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false));
+ 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));
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);