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

Allow game folder migration to fail gracefully when cleanup cannot completely succeed

This commit is contained in:
Dean Herbert 2022-02-10 18:48:37 +09:00
parent 7081c418dd
commit 44f2d8a448
5 changed files with 66 additions and 20 deletions

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tournament.IO
public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty); public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty);
public override void Migrate(Storage newStorage) public override bool Migrate(Storage newStorage)
{ {
// this migration only happens once on moving to the per-tournament storage system. // this migration only happens once on moving to the per-tournament storage system.
// listed files are those known at that point in time. // listed files are those known at that point in time.
@ -94,6 +94,8 @@ namespace osu.Game.Tournament.IO
ChangeTargetStorage(newStorage); ChangeTargetStorage(newStorage);
storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament); storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament);
storageConfig.Save(); storageConfig.Save();
return true;
} }
private void moveFileIfExists(string file, DirectoryInfo destination) private void moveFileIfExists(string file, DirectoryInfo destination)

View File

@ -33,7 +33,8 @@ namespace osu.Game.IO
/// A general purpose migration method to move the storage to a different location. /// A general purpose migration method to move the storage to a different location.
/// <param name="newStorage">The target storage of the migration.</param> /// <param name="newStorage">The target storage of the migration.</param>
/// </summary> /// </summary>
public virtual void Migrate(Storage newStorage) /// <returns>Whether cleanup could complete.</returns>
public virtual bool Migrate(Storage newStorage)
{ {
var source = new DirectoryInfo(GetFullPath(".")); var source = new DirectoryInfo(GetFullPath("."));
var destination = new DirectoryInfo(newStorage.GetFullPath(".")); var destination = new DirectoryInfo(newStorage.GetFullPath("."));
@ -57,17 +58,20 @@ namespace osu.Game.IO
CopyRecursive(source, destination); CopyRecursive(source, destination);
ChangeTargetStorage(newStorage); ChangeTargetStorage(newStorage);
DeleteRecursive(source);
return DeleteRecursive(source);
} }
protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) protected bool DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true)
{ {
bool allFilesDeleted = true;
foreach (System.IO.FileInfo fi in target.GetFiles()) foreach (System.IO.FileInfo fi in target.GetFiles())
{ {
if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) if (topLevelExcludes && IgnoreFiles.Contains(fi.Name))
continue; continue;
AttemptOperation(() => fi.Delete()); allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false);
} }
foreach (DirectoryInfo dir in target.GetDirectories()) foreach (DirectoryInfo dir in target.GetDirectories())
@ -75,11 +79,13 @@ namespace osu.Game.IO
if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name))
continue; continue;
AttemptOperation(() => dir.Delete(true)); allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false);
} }
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
AttemptOperation(target.Delete); allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false);
return allFilesDeleted;
} }
protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
@ -110,19 +116,25 @@ namespace osu.Game.IO
/// </summary> /// </summary>
/// <param name="action">The action to perform.</param> /// <param name="action">The action to perform.</param>
/// <param name="attempts">The number of attempts (250ms wait between each).</param> /// <param name="attempts">The number of attempts (250ms wait between each).</param>
protected static void AttemptOperation(Action action, int attempts = 10) /// <param name="throwOnFailure">Whether to throw an exception on failure. If <c>false</c>, will silently fail.</param>
protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true)
{ {
while (true) while (true)
{ {
try try
{ {
action(); action();
return; return true;
} }
catch (Exception) catch (Exception)
{ {
if (attempts-- == 0) if (attempts-- == 0)
throw; {
if (throwOnFailure)
throw;
return false;
}
} }
Thread.Sleep(250); Thread.Sleep(250);

View File

@ -113,11 +113,14 @@ namespace osu.Game.IO
} }
} }
public override void Migrate(Storage newStorage) public override bool Migrate(Storage newStorage)
{ {
base.Migrate(newStorage); bool cleanupSucceeded = base.Migrate(newStorage);
storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath(".")); storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath("."));
storageConfig.Save(); storageConfig.Save();
return cleanupSucceeded;
} }
} }

View File

@ -413,7 +413,7 @@ namespace osu.Game
Scheduler.AddDelayed(GracefullyExit, 2000); Scheduler.AddDelayed(GracefullyExit, 2000);
} }
public void Migrate(string path) public bool Migrate(string path)
{ {
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
@ -432,14 +432,15 @@ namespace osu.Game
readyToRun.Wait(); readyToRun.Wait();
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
Logger.Log(@"Migration complete!");
return cleanupSucceded != false;
} }
finally finally
{ {
realmBlocker?.Dispose(); realmBlocker?.Dispose();
} }
Logger.Log(@"Migration complete!");
} }
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();

View File

@ -4,13 +4,16 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens; using osu.Game.Screens;
using osuTK; using osuTK;
@ -23,6 +26,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private OsuGame game { get; set; } private OsuGame game { get; set; }
[Resolved]
private NotificationOverlay notifications { get; set; }
[Resolved]
private Storage storage { get; set; }
[Resolved]
private GameHost host { get; set; }
public override bool AllowBackButton => false; public override bool AllowBackButton => false;
public override bool AllowExternalScreenChange => false; public override bool AllowExternalScreenChange => false;
@ -84,17 +96,33 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Beatmap.Value = Beatmap.Default; Beatmap.Value = Beatmap.Default;
var originalStorage = new NativeStorage(storage.GetFullPath(string.Empty), host);
migrationTask = Task.Run(PerformMigration) migrationTask = Task.Run(PerformMigration)
.ContinueWith(t => .ContinueWith(task =>
{ {
if (t.IsFaulted) if (task.IsFaulted)
Logger.Error(t.Exception, $"Error during migration: {t.Exception?.Message}"); {
Logger.Error(task.Exception, $"Error during migration: {task.Exception?.Message}");
}
else if (!task.GetResultSafely())
{
notifications.Post(new SimpleNotification
{
Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.",
Activated = () =>
{
originalStorage.PresentExternally();
return true;
}
});
}
Schedule(this.Exit); Schedule(this.Exit);
}); });
} }
protected virtual void PerformMigration() => game?.Migrate(destination.FullName); protected virtual bool PerformMigration() => game?.Migrate(destination.FullName) != false;
public override void OnEntering(IScreen last) public override void OnEntering(IScreen last)
{ {