mirror of
https://github.com/ppy/osu.git
synced 2025-02-05 05:43:21 +08:00
Merge pull request #9256 from MiraiSubject/tourney-asset-refactor
This commit is contained in:
commit
ca1c287664
@ -111,6 +111,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
var osu = LoadOsuIntoHost(host);
|
var osu = LoadOsuIntoHost(host);
|
||||||
var storage = osu.Dependencies.Get<Storage>();
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
var osuStorage = storage as MigratableStorage;
|
||||||
|
|
||||||
// Store the current storage's path. We'll need to refer to this for assertions in the original directory after the migration completes.
|
// Store the current storage's path. We'll need to refer to this for assertions in the original directory after the migration completes.
|
||||||
string originalDirectory = storage.GetFullPath(".");
|
string originalDirectory = storage.GetFullPath(".");
|
||||||
@ -137,13 +138,15 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(!Directory.Exists(Path.Combine(originalDirectory, "test-nested", "cache")));
|
Assert.That(!Directory.Exists(Path.Combine(originalDirectory, "test-nested", "cache")));
|
||||||
Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache")));
|
Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache")));
|
||||||
|
|
||||||
foreach (var file in OsuStorage.IGNORE_FILES)
|
Assert.That(osuStorage, Is.Not.Null);
|
||||||
|
|
||||||
|
foreach (var file in osuStorage.IgnoreFiles)
|
||||||
{
|
{
|
||||||
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
|
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
|
||||||
Assert.That(storage.Exists(file), Is.False);
|
Assert.That(storage.Exists(file), Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dir in OsuStorage.IGNORE_DIRECTORIES)
|
foreach (var dir in osuStorage.IgnoreDirectories)
|
||||||
{
|
{
|
||||||
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
|
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
|
||||||
Assert.That(storage.ExistsDirectory(dir), Is.False);
|
Assert.That(storage.ExistsDirectory(dir), Is.False);
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
// 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Tournament.Configuration;
|
||||||
|
using osu.Game.Tests;
|
||||||
|
|
||||||
|
namespace osu.Game.Tournament.Tests.NonVisual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CustomTourneyDirectoryTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultDirectory()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomDirectory()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file.
|
||||||
|
{
|
||||||
|
string osuDesktopStorage = basePath(nameof(TestCustomDirectory));
|
||||||
|
const string custom_tournament = "custom";
|
||||||
|
|
||||||
|
// need access before the game has constructed its own storage yet.
|
||||||
|
Storage storage = new DesktopStorage(osuDesktopStorage, host);
|
||||||
|
// manual cleaning so we can prepare a config file.
|
||||||
|
storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
|
using (var storageConfig = new TournamentStorageManager(storage))
|
||||||
|
storageConfig.Set(StorageConfig.CurrentTournament, custom_tournament);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", custom_tournament)));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMigration()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration.
|
||||||
|
{
|
||||||
|
string osuRoot = basePath(nameof(TestMigration));
|
||||||
|
string configFile = Path.Combine(osuRoot, "tournament.ini");
|
||||||
|
|
||||||
|
if (File.Exists(configFile))
|
||||||
|
File.Delete(configFile);
|
||||||
|
|
||||||
|
// Recreate the old setup that uses "tournament" as the base path.
|
||||||
|
string oldPath = Path.Combine(osuRoot, "tournament");
|
||||||
|
|
||||||
|
string videosPath = Path.Combine(oldPath, "videos");
|
||||||
|
string modsPath = Path.Combine(oldPath, "mods");
|
||||||
|
string flagsPath = Path.Combine(oldPath, "flags");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(videosPath);
|
||||||
|
Directory.CreateDirectory(modsPath);
|
||||||
|
Directory.CreateDirectory(flagsPath);
|
||||||
|
|
||||||
|
// Define testing files corresponding to the specific file migrations that are needed
|
||||||
|
string bracketFile = Path.Combine(osuRoot, "bracket.json");
|
||||||
|
|
||||||
|
string drawingsConfig = Path.Combine(osuRoot, "drawings.ini");
|
||||||
|
string drawingsFile = Path.Combine(osuRoot, "drawings.txt");
|
||||||
|
string drawingsResult = Path.Combine(osuRoot, "drawings_results.txt");
|
||||||
|
|
||||||
|
// Define sample files to test recursive copying
|
||||||
|
string videoFile = Path.Combine(videosPath, "video.mp4");
|
||||||
|
string modFile = Path.Combine(modsPath, "mod.png");
|
||||||
|
string flagFile = Path.Combine(flagsPath, "flag.png");
|
||||||
|
|
||||||
|
File.WriteAllText(bracketFile, "{}");
|
||||||
|
File.WriteAllText(drawingsConfig, "test");
|
||||||
|
File.WriteAllText(drawingsFile, "test");
|
||||||
|
File.WriteAllText(drawingsResult, "test");
|
||||||
|
File.WriteAllText(videoFile, "test");
|
||||||
|
File.WriteAllText(modFile, "test");
|
||||||
|
File.WriteAllText(flagFile, "test");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
|
string migratedPath = Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default");
|
||||||
|
|
||||||
|
videosPath = Path.Combine(migratedPath, "videos");
|
||||||
|
modsPath = Path.Combine(migratedPath, "mods");
|
||||||
|
flagsPath = Path.Combine(migratedPath, "flags");
|
||||||
|
|
||||||
|
videoFile = Path.Combine(videosPath, "video.mp4");
|
||||||
|
modFile = Path.Combine(modsPath, "mod.png");
|
||||||
|
flagFile = Path.Combine(flagsPath, "flag.png");
|
||||||
|
|
||||||
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath));
|
||||||
|
|
||||||
|
Assert.True(storage.Exists("bracket.json"));
|
||||||
|
Assert.True(storage.Exists("drawings.txt"));
|
||||||
|
Assert.True(storage.Exists("drawings_results.txt"));
|
||||||
|
|
||||||
|
Assert.True(storage.Exists("drawings.ini"));
|
||||||
|
|
||||||
|
Assert.True(storage.Exists(videoFile));
|
||||||
|
Assert.True(storage.Exists(modFile));
|
||||||
|
Assert.True(storage.Exists(flagFile));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Storage.Delete("tournament.ini");
|
||||||
|
host.Storage.DeleteDirectory("tournaments");
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TournamentGameBase loadOsu(GameHost host)
|
||||||
|
{
|
||||||
|
var osu = new TournamentGameBase();
|
||||||
|
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 = 90000)
|
||||||
|
{
|
||||||
|
Task task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
while (!result()) Thread.Sleep(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.Video;
|
using osu.Framework.Graphics.Video;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Tournament.IO;
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Components
|
namespace osu.Game.Tournament.Components
|
||||||
{
|
{
|
||||||
@ -17,7 +18,6 @@ namespace osu.Game.Tournament.Components
|
|||||||
private readonly string filename;
|
private readonly string filename;
|
||||||
private readonly bool drawFallbackGradient;
|
private readonly bool drawFallbackGradient;
|
||||||
private Video video;
|
private Video video;
|
||||||
|
|
||||||
private ManualClock manualClock;
|
private ManualClock manualClock;
|
||||||
|
|
||||||
public TourneyVideo(string filename, bool drawFallbackGradient = false)
|
public TourneyVideo(string filename, bool drawFallbackGradient = false)
|
||||||
@ -27,9 +27,9 @@ namespace osu.Game.Tournament.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TournamentStorage storage)
|
private void load(TournamentVideoResourceStore storage)
|
||||||
{
|
{
|
||||||
var stream = storage.GetStream($@"videos/{filename}");
|
var stream = storage.GetStream(filename);
|
||||||
|
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
// 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.Tournament.Configuration
|
||||||
|
{
|
||||||
|
public class TournamentStorageManager : IniConfigManager<StorageConfig>
|
||||||
|
{
|
||||||
|
protected override string Filename => "tournament.ini";
|
||||||
|
|
||||||
|
public TournamentStorageManager(Storage storage)
|
||||||
|
: base(storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StorageConfig
|
||||||
|
{
|
||||||
|
CurrentTournament,
|
||||||
|
}
|
||||||
|
}
|
72
osu.Game.Tournament/IO/TournamentStorage.cs
Normal file
72
osu.Game.Tournament/IO/TournamentStorage.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.IO;
|
||||||
|
using osu.Game.Tournament.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Game.Tournament.IO
|
||||||
|
{
|
||||||
|
public class TournamentStorage : MigratableStorage
|
||||||
|
{
|
||||||
|
private const string default_tournament = "default";
|
||||||
|
private readonly Storage storage;
|
||||||
|
private readonly TournamentStorageManager storageConfig;
|
||||||
|
|
||||||
|
public TournamentStorage(Storage storage)
|
||||||
|
: base(storage.GetStorageForDirectory("tournaments"), string.Empty)
|
||||||
|
{
|
||||||
|
this.storage = storage;
|
||||||
|
|
||||||
|
storageConfig = new TournamentStorageManager(storage);
|
||||||
|
|
||||||
|
if (storage.Exists("tournament.ini"))
|
||||||
|
{
|
||||||
|
ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(storageConfig.Get<string>(StorageConfig.CurrentTournament)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Migrate(UnderlyingStorage.GetStorageForDirectory(default_tournament));
|
||||||
|
|
||||||
|
Logger.Log("Using tournament storage: " + GetFullPath(string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Migrate(Storage newStorage)
|
||||||
|
{
|
||||||
|
// this migration only happens once on moving to the per-tournament storage system.
|
||||||
|
// listed files are those known at that point in time.
|
||||||
|
// this can be removed at some point in the future (6 months obsoletion would mean 2021-04-19)
|
||||||
|
|
||||||
|
var source = new DirectoryInfo(storage.GetFullPath("tournament"));
|
||||||
|
var destination = new DirectoryInfo(newStorage.GetFullPath("."));
|
||||||
|
|
||||||
|
if (source.Exists)
|
||||||
|
{
|
||||||
|
Logger.Log("Migrating tournament assets to default tournament storage.");
|
||||||
|
CopyRecursive(source, destination);
|
||||||
|
DeleteRecursive(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveFileIfExists("bracket.json", destination);
|
||||||
|
moveFileIfExists("drawings.txt", destination);
|
||||||
|
moveFileIfExists("drawings_results.txt", destination);
|
||||||
|
moveFileIfExists("drawings.ini", destination);
|
||||||
|
|
||||||
|
ChangeTargetStorage(newStorage);
|
||||||
|
storageConfig.Set(StorageConfig.CurrentTournament, default_tournament);
|
||||||
|
storageConfig.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveFileIfExists(string file, DirectoryInfo destination)
|
||||||
|
{
|
||||||
|
if (!storage.Exists(file))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Logger.Log($"Migrating {file} to default tournament storage.");
|
||||||
|
var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file));
|
||||||
|
AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true));
|
||||||
|
fileInfo.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,12 @@
|
|||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
namespace osu.Game.Tournament
|
namespace osu.Game.Tournament.IO
|
||||||
{
|
{
|
||||||
internal class TournamentStorage : NamespacedResourceStore<byte[]>
|
public class TournamentVideoResourceStore : NamespacedResourceStore<byte[]>
|
||||||
{
|
{
|
||||||
public TournamentStorage(Storage storage)
|
public TournamentVideoResourceStore(Storage storage)
|
||||||
: base(new StorageBackedResourceStore(storage), "tournament")
|
: base(new StorageBackedResourceStore(storage), "videos")
|
||||||
{
|
{
|
||||||
AddExtension("m4v");
|
AddExtension("m4v");
|
||||||
AddExtension("avi");
|
AddExtension("avi");
|
@ -8,11 +8,12 @@ using Newtonsoft.Json;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Tournament.IPC;
|
using osu.Game.Tournament.IPC;
|
||||||
|
using osu.Game.Tournament.IO;
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -23,13 +24,8 @@ namespace osu.Game.Tournament
|
|||||||
public class TournamentGameBase : OsuGameBase
|
public class TournamentGameBase : OsuGameBase
|
||||||
{
|
{
|
||||||
private const string bracket_filename = "bracket.json";
|
private const string bracket_filename = "bracket.json";
|
||||||
|
|
||||||
private LadderInfo ladder;
|
private LadderInfo ladder;
|
||||||
|
private TournamentStorage storage;
|
||||||
private Storage storage;
|
|
||||||
|
|
||||||
private TournamentStorage tournamentStorage;
|
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
private FileBasedIPC ipc;
|
private FileBasedIPC ipc;
|
||||||
|
|
||||||
@ -39,15 +35,14 @@ namespace osu.Game.Tournament
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(Storage storage)
|
private void load(Storage baseStorage)
|
||||||
{
|
{
|
||||||
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
|
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
|
||||||
|
|
||||||
dependencies.CacheAs(tournamentStorage = new TournamentStorage(storage));
|
dependencies.CacheAs<Storage>(storage = new TournamentStorage(baseStorage));
|
||||||
|
dependencies.Cache(new TournamentVideoResourceStore(storage));
|
||||||
|
|
||||||
Textures.AddStore(new TextureLoaderStore(tournamentStorage));
|
Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage)));
|
||||||
|
|
||||||
this.storage = storage;
|
|
||||||
|
|
||||||
readBracket();
|
readBracket();
|
||||||
|
|
||||||
|
132
osu.Game/IO/MigratableStorage.cs
Normal file
132
osu.Game/IO/MigratableStorage.cs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// 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 osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace osu.Game.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="WrappedStorage"/> that is migratable to different locations.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MigratableStorage : WrappedStorage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A relative list of directory paths which should not be migrated.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string[] IgnoreDirectories => Array.Empty<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A relative list of file paths which should not be migrated.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string[] IgnoreFiles => Array.Empty<string>();
|
||||||
|
|
||||||
|
protected MigratableStorage(Storage storage, string subPath = null)
|
||||||
|
: base(storage, subPath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A general purpose migration method to move the storage to a different location.
|
||||||
|
/// <param name="newStorage">The target storage of the migration.</param>
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Migrate(Storage newStorage)
|
||||||
|
{
|
||||||
|
var source = new DirectoryInfo(GetFullPath("."));
|
||||||
|
var destination = new DirectoryInfo(newStorage.GetFullPath("."));
|
||||||
|
|
||||||
|
// using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620)
|
||||||
|
var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar);
|
||||||
|
var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
if (sourceUri == destinationUri)
|
||||||
|
throw new ArgumentException("Destination provided is already the current location", destination.FullName);
|
||||||
|
|
||||||
|
if (sourceUri.IsBaseOf(destinationUri))
|
||||||
|
throw new ArgumentException("Destination provided is inside the source", destination.FullName);
|
||||||
|
|
||||||
|
// ensure the new location has no files present, else hard abort
|
||||||
|
if (destination.Exists)
|
||||||
|
{
|
||||||
|
if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0)
|
||||||
|
throw new ArgumentException("Destination provided already has files or directories present", destination.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyRecursive(source, destination);
|
||||||
|
ChangeTargetStorage(newStorage);
|
||||||
|
DeleteRecursive(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true)
|
||||||
|
{
|
||||||
|
foreach (System.IO.FileInfo fi in target.GetFiles())
|
||||||
|
{
|
||||||
|
if (topLevelExcludes && IgnoreFiles.Contains(fi.Name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
AttemptOperation(() => fi.Delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DirectoryInfo dir in target.GetDirectories())
|
||||||
|
{
|
||||||
|
if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
AttemptOperation(() => dir.Delete(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
||||||
|
AttemptOperation(target.Delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
||||||
|
{
|
||||||
|
// based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo
|
||||||
|
if (!destination.Exists)
|
||||||
|
Directory.CreateDirectory(destination.FullName);
|
||||||
|
|
||||||
|
foreach (System.IO.FileInfo fi in source.GetFiles())
|
||||||
|
{
|
||||||
|
if (topLevelExcludes && IgnoreFiles.Contains(fi.Name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DirectoryInfo dir in source.GetDirectories())
|
||||||
|
{
|
||||||
|
if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">The action to perform.</param>
|
||||||
|
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
||||||
|
protected static void AttemptOperation(Action action, int attempts = 10)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (attempts-- == 0)
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -13,7 +10,7 @@ using osu.Game.Configuration;
|
|||||||
|
|
||||||
namespace osu.Game.IO
|
namespace osu.Game.IO
|
||||||
{
|
{
|
||||||
public class OsuStorage : WrappedStorage
|
public class OsuStorage : MigratableStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the error (if any) that occurred when initialising the custom storage during initial startup.
|
/// Indicates the error (if any) that occurred when initialising the custom storage during initial startup.
|
||||||
@ -36,9 +33,9 @@ namespace osu.Game.IO
|
|||||||
private readonly StorageConfigManager storageConfig;
|
private readonly StorageConfigManager storageConfig;
|
||||||
private readonly Storage defaultStorage;
|
private readonly Storage defaultStorage;
|
||||||
|
|
||||||
public static readonly string[] IGNORE_DIRECTORIES = { "cache" };
|
public override string[] IgnoreDirectories => new[] { "cache" };
|
||||||
|
|
||||||
public static readonly string[] IGNORE_FILES =
|
public override string[] IgnoreFiles => new[]
|
||||||
{
|
{
|
||||||
"framework.ini",
|
"framework.ini",
|
||||||
"storage.ini"
|
"storage.ini"
|
||||||
@ -103,106 +100,11 @@ namespace osu.Game.IO
|
|||||||
Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs");
|
Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Migrate(string newLocation)
|
public override void Migrate(Storage newStorage)
|
||||||
{
|
{
|
||||||
var source = new DirectoryInfo(GetFullPath("."));
|
base.Migrate(newStorage);
|
||||||
var destination = new DirectoryInfo(newLocation);
|
storageConfig.Set(StorageConfig.FullPath, newStorage.GetFullPath("."));
|
||||||
|
|
||||||
// using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620)
|
|
||||||
var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar);
|
|
||||||
var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar);
|
|
||||||
|
|
||||||
if (sourceUri == destinationUri)
|
|
||||||
throw new ArgumentException("Destination provided is already the current location", nameof(newLocation));
|
|
||||||
|
|
||||||
if (sourceUri.IsBaseOf(destinationUri))
|
|
||||||
throw new ArgumentException("Destination provided is inside the source", nameof(newLocation));
|
|
||||||
|
|
||||||
// ensure the new location has no files present, else hard abort
|
|
||||||
if (destination.Exists)
|
|
||||||
{
|
|
||||||
if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0)
|
|
||||||
throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation));
|
|
||||||
|
|
||||||
deleteRecursive(destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
copyRecursive(source, destination);
|
|
||||||
|
|
||||||
ChangeTargetStorage(host.GetStorage(newLocation));
|
|
||||||
|
|
||||||
storageConfig.Set(StorageConfig.FullPath, newLocation);
|
|
||||||
storageConfig.Save();
|
storageConfig.Save();
|
||||||
|
|
||||||
deleteRecursive(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true)
|
|
||||||
{
|
|
||||||
foreach (System.IO.FileInfo fi in target.GetFiles())
|
|
||||||
{
|
|
||||||
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
attemptOperation(() => fi.Delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryInfo dir in target.GetDirectories())
|
|
||||||
{
|
|
||||||
if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
attemptOperation(() => dir.Delete(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
|
||||||
attemptOperation(target.Delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
|
||||||
{
|
|
||||||
// based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo
|
|
||||||
Directory.CreateDirectory(destination.FullName);
|
|
||||||
|
|
||||||
foreach (System.IO.FileInfo fi in source.GetFiles())
|
|
||||||
{
|
|
||||||
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryInfo dir in source.GetDirectories())
|
|
||||||
{
|
|
||||||
if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action">The action to perform.</param>
|
|
||||||
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
|
||||||
private static void attemptOperation(Action action, int attempts = 10)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (attempts-- == 0)
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.Sleep(250);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,7 +406,7 @@ namespace osu.Game
|
|||||||
public void Migrate(string path)
|
public void Migrate(string path)
|
||||||
{
|
{
|
||||||
contextFactory.FlushConnections();
|
contextFactory.FlushConnections();
|
||||||
(Storage as OsuStorage)?.Migrate(path);
|
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user