From 1fb3d11591858b9f0e05e35f41e4fbf4840b3b0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Mar 2022 18:00:56 +0900 Subject: [PATCH 1/3] Add ability to "migrate" data to another folder which has an existing install Until now, migrating would always attempt to move files. There's a chance that a user is reinstalling osu! but has their data at a custom location. We want to allow the chance for them to continue using the external data. This seems like the easiest way to make it work. Would be nice if we had a `Game.Restart()` method, but maybe this is enough for now? Note that further down the road we will probably prompt the user to potentially select a custom install path (including one with existing data) before osu! gets to writing anything. --- osu.Game/IO/OsuStorage.cs | 17 +++++++--- .../Maintenance/MigrationSelectScreen.cs | 32 ++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 6e7cb545e3..a3f7b4bfec 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -64,12 +64,22 @@ namespace osu.Game.IO /// public void ResetCustomStoragePath() { - storageConfig.SetValue(StorageConfig.FullPath, string.Empty); - storageConfig.Save(); + ChangeDataPath(string.Empty); ChangeTargetStorage(defaultStorage); } + /// + /// Updates the target data path without immediately switching. + /// This does NOT migrate any data. + /// The game should immediately be restarted after calling this. + /// + public void ChangeDataPath(string newPath) + { + storageConfig.SetValue(StorageConfig.FullPath, newPath); + storageConfig.Save(); + } + /// /// Attempts to change to the user's custom storage path. /// @@ -117,8 +127,7 @@ namespace osu.Game.IO { bool cleanupSucceeded = base.Migrate(newStorage); - storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath(".")); - storageConfig.Save(); + ChangeDataPath(newStorage.GetFullPath(".")); return cleanupSucceeded; } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 1a60ab0638..9b20f2c0c3 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -3,11 +3,14 @@ using System; using System.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Game.IO; +using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -16,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved] private Storage storage { get; set; } + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved(canBeNull: true)] + private DialogOverlay dialogOverlay { get; set; } + protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; public override bool AllowExternalScreenChange => false; @@ -32,8 +41,29 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance try { - if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0) + var directoryInfos = target.GetDirectories(); + var fileInfos = target.GetFiles(); + + if (directoryInfos.Length > 0 || fileInfos.Length > 0) + { + // Quick test for whether there's already an osu! install at the target path. + if (fileInfos.Any(f => f.Name == @"client.realm")) + { + dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use this data instead?", () => + { + dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () => + { + (storage as OsuStorage)?.ChangeDataPath(target.FullName); + game.GracefullyExit(); + }, () => { })); + }, + () => { })); + + return; + } + target = target.CreateSubdirectory("osu-lazer"); + } } catch (Exception e) { From 4741679a9404f08d79e0c412bc0f641b5c7d8c86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Mar 2022 19:03:50 +0900 Subject: [PATCH 2/3] Change confirmation message to be more clear about intentions Co-authored-by: Henry Lin --- .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 9b20f2c0c3..047587e084 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance // Quick test for whether there's already an osu! install at the target path. if (fileInfos.Any(f => f.Name == @"client.realm")) { - dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use this data instead?", () => + dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use that data instead?", () => { dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () => { From f762af13446554683388ed420bf97b0c9c6d807c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Mar 2022 23:28:07 +0900 Subject: [PATCH 3/3] Add test coverage of migrating to folder with existing data --- .../NonVisual/CustomDataDirectoryTest.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 834930a05e..b5ab33b9fc 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -233,6 +233,48 @@ namespace osu.Game.Tests.NonVisual } } + [Test] + public void TestMigrationFailsOnExistingData() + { + string customPath = prepareCustomPath(); + string customPath2 = prepareCustomPath(); + + using (var host = new CustomTestHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host); + + var storage = osu.Dependencies.Get(); + var osuStorage = storage as OsuStorage; + + string originalDirectory = storage.GetFullPath("."); + + const string database_filename = "client.realm"; + + Assert.DoesNotThrow(() => osu.Migrate(customPath)); + Assert.That(File.Exists(Path.Combine(customPath, database_filename))); + + Directory.CreateDirectory(customPath2); + File.Copy(Path.Combine(customPath, database_filename), Path.Combine(customPath2, database_filename)); + + // Fails because file already exists. + Assert.Throws(() => osu.Migrate(customPath2)); + + osuStorage?.ChangeDataPath(customPath2); + + Assert.That(osuStorage?.CustomStoragePath, Is.EqualTo(customPath2)); + Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath2}")); + } + finally + { + host.Exit(); + cleanupPath(customPath); + cleanupPath(customPath2); + } + } + } + [Test] public void TestMigrationToNestedTargetFails() {