1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:47:27 +08:00

Merge pull request #8935 from peppy/custom-data-directory

Add basic custom data directory support
This commit is contained in:
Dan Balasescu 2020-05-11 12:49:56 +09:00 committed by GitHub
commit 7072207c7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 277 additions and 2 deletions

View File

@ -0,0 +1,129 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Game.Configuration;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class CustomDataDirectoryTest
{
[Test]
public void TestDefaultDirectory()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory)))
{
try
{
var osu = loadOsu(host);
var storage = osu.Dependencies.Get<Storage>();
string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestDefaultDirectory));
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
}
finally
{
host.Exit();
}
}
}
private string customPath => Path.Combine(Environment.CurrentDirectory, "custom-path");
[Test]
public void TestCustomDirectory()
{
using (var host = new HeadlessGameHost(nameof(TestCustomDirectory)))
{
string headlessPrefix = Path.Combine("headless", nameof(TestCustomDirectory));
// need access before the game has constructed its own storage yet.
Storage storage = new DesktopStorage(headlessPrefix, host);
// manual cleaning so we can prepare a config file.
storage.DeleteDirectory(string.Empty);
using (var storageConfig = new StorageConfigManager(storage))
storageConfig.Set(StorageConfig.FullPath, customPath);
try
{
var osu = loadOsu(host);
// switch to DI'd storage
storage = osu.Dependencies.Get<Storage>();
Assert.That(storage.GetFullPath("."), Is.EqualTo(customPath));
}
finally
{
host.Exit();
}
}
}
[Test]
public void TestSubDirectoryLookup()
{
using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup)))
{
string headlessPrefix = Path.Combine("headless", nameof(TestSubDirectoryLookup));
// need access before the game has constructed its own storage yet.
Storage storage = new DesktopStorage(headlessPrefix, host);
// manual cleaning so we can prepare a config file.
storage.DeleteDirectory(string.Empty);
using (var storageConfig = new StorageConfigManager(storage))
storageConfig.Set(StorageConfig.FullPath, customPath);
try
{
var osu = loadOsu(host);
// switch to DI'd storage
storage = osu.Dependencies.Get<Storage>();
string actualTestFile = Path.Combine(customPath, "rulesets", "test");
File.WriteAllText(actualTestFile, "test");
var rulesetStorage = storage.GetStorageForDirectory("rulesets");
var lookupPath = rulesetStorage.GetFiles(".").Single();
Assert.That(lookupPath, Is.EqualTo("test"));
}
finally
{
host.Exit();
}
}
}
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
{
Task task = Task.Run(() =>
{
while (!result()) Thread.Sleep(200);
});
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Configuration;
using osu.Framework.Platform;
namespace osu.Game.Configuration
{
public class StorageConfigManager : IniConfigManager<StorageConfig>
{
protected override string Filename => "storage.ini";
public StorageConfigManager(Storage storage)
: base(storage)
{
}
protected override void InitialiseDefaults()
{
base.InitialiseDefaults();
Set(StorageConfig.FullPath, string.Empty);
}
}
public enum StorageConfig
{
FullPath,
}
}

26
osu.Game/IO/OsuStorage.cs Normal file
View File

@ -0,0 +1,26 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Configuration;
namespace osu.Game.IO
{
public class OsuStorage : WrappedStorage
{
public OsuStorage(GameHost host)
: base(host.Storage, string.Empty)
{
var storageConfig = new StorageConfigManager(host.Storage);
var customStoragePath = storageConfig.Get<string>(StorageConfig.FullPath);
if (!string.IsNullOrEmpty(customStoragePath))
{
ChangeTargetStorage(host.GetStorage(customStoragePath));
Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs");
}
}
}
}

View File

@ -0,0 +1,88 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.IO;
using osu.Framework.Platform;
namespace osu.Game.IO
{
/// <summary>
/// A storage which wraps another storage and delegates implementation, potentially mutating the lookup path.
/// </summary>
public class WrappedStorage : Storage
{
protected Storage UnderlyingStorage { get; private set; }
private readonly string subPath;
public WrappedStorage(Storage underlyingStorage, string subPath = null)
: base(string.Empty)
{
ChangeTargetStorage(underlyingStorage);
this.subPath = subPath;
}
protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path;
protected void ChangeTargetStorage(Storage newStorage)
{
UnderlyingStorage = newStorage;
}
public override string GetFullPath(string path, bool createIfNotExisting = false) =>
UnderlyingStorage.GetFullPath(MutatePath(path), createIfNotExisting);
public override bool Exists(string path) =>
UnderlyingStorage.Exists(MutatePath(path));
public override bool ExistsDirectory(string path) =>
UnderlyingStorage.ExistsDirectory(MutatePath(path));
public override void DeleteDirectory(string path) =>
UnderlyingStorage.DeleteDirectory(MutatePath(path));
public override void Delete(string path) =>
UnderlyingStorage.Delete(MutatePath(path));
public override IEnumerable<string> GetDirectories(string path) =>
ToLocalRelative(UnderlyingStorage.GetDirectories(MutatePath(path)));
public IEnumerable<string> ToLocalRelative(IEnumerable<string> paths)
{
string localRoot = GetFullPath(string.Empty);
foreach (var path in paths)
yield return Path.GetRelativePath(localRoot, UnderlyingStorage.GetFullPath(path));
}
public override IEnumerable<string> GetFiles(string path, string pattern = "*") =>
ToLocalRelative(UnderlyingStorage.GetFiles(MutatePath(path), pattern));
public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) =>
UnderlyingStorage.GetStream(MutatePath(path), access, mode);
public override string GetDatabaseConnectionString(string name) =>
UnderlyingStorage.GetDatabaseConnectionString(MutatePath(name));
public override void DeleteDatabase(string name) => UnderlyingStorage.DeleteDatabase(MutatePath(name));
public override void OpenInNativeExplorer() => UnderlyingStorage.OpenInNativeExplorer();
public override Storage GetStorageForDirectory(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Must be non-null and not empty string", nameof(path));
if (!path.EndsWith(Path.DirectorySeparatorChar))
path += Path.DirectorySeparatorChar;
// create non-existing path.
GetFullPath(path, true);
return new WrappedStorage(this, path);
}
}
}

View File

@ -132,6 +132,8 @@ namespace osu.Game
dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage));
dependencies.CacheAs(Storage);
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore()));
dependencies.Cache(largeStore);
@ -300,8 +302,8 @@ namespace osu.Game
{
base.SetHost(host);
if (Storage == null)
Storage = host.Storage;
if (Storage == null) // may be non-null for certain tests
Storage = new OsuStorage(host);
if (LocalConfig == null)
LocalConfig = new OsuConfigManager(Storage);