1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 05:27:23 +08:00

Merge branch 'master' into ruleset-id-fixes

This commit is contained in:
Dean Herbert 2022-01-27 17:07:18 +09:00
commit a5d422e82c
10 changed files with 120 additions and 28 deletions

View File

@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database
} }
protected static RulesetInfo CreateRuleset() => protected static RulesetInfo CreateRuleset() =>
new RulesetInfo(0, "osu!", "osu", true); new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true };
private class RealmTestGame : Framework.Game private class RealmTestGame : Framework.Game
{ {

View File

@ -4,11 +4,13 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Select;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
@ -116,5 +118,24 @@ namespace osu.Game.Tests.Visual.Editing
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
} }
[Test]
public void TestExitWithoutSaveFromExistingBeatmap()
{
const string tags_to_save = "these tags will be saved";
const string tags_to_discard = "these tags should be discarded";
AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save);
SaveEditor();
AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
ReloadEditorToSameBeatmap();
AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard);
AddStep("Exit editor", () => Editor.Exit());
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
}
} }
} }

View File

@ -70,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () => AddUntilStep("background has correct params", () =>
{ {
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(); // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
// due to the beatmap refetch logic ran on editor suspend.
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
}); });
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
@ -99,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () => AddUntilStep("background has correct params", () =>
{ {
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(); // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
// due to the beatmap refetch logic ran on editor suspend.
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
}); });

View File

@ -1,6 +1,7 @@
// 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.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -9,17 +10,24 @@ using osu.Framework.Development;
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.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
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.Models; using osu.Game.Models;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
using Realms; using Realms;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Writers.Zip;
#nullable enable #nullable enable
@ -40,6 +48,15 @@ namespace osu.Game.Database
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } = null!; private OsuConfigManager config { get; set; } = null!;
[Resolved]
private NotificationOverlay notificationOverlay { get; set; } = null!;
[Resolved]
private OsuGame game { get; set; } = null!;
[Resolved]
private Storage storage { get; set; } = null!;
private readonly OsuSpriteText currentOperationText; private readonly OsuSpriteText currentOperationText;
public EFToRealmMigrator() public EFToRealmMigrator()
@ -96,7 +113,11 @@ namespace osu.Game.Database
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
beginMigration();
}
private void beginMigration()
{
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
using (var ef = efContextFactory.Get()) using (var ef = efContextFactory.Get())
@ -117,21 +138,67 @@ namespace osu.Game.Database
migrateBeatmaps(ef); migrateBeatmaps(ef);
migrateScores(ef); migrateScores(ef);
} }
}, TaskCreationOptions.LongRunning).ContinueWith(t =>
// Delete the database permanently. {
// Will cause future startups to not attempt migration. if (t.Exception == null)
log("Migration successful, deleting EF database"); {
efContextFactory.ResetDatabase(); log("Migration successful!");
if (DebugUtils.IsDebugBuild) if (DebugUtils.IsDebugBuild)
Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important);
}, TaskCreationOptions.LongRunning).ContinueWith(t => }
else
{ {
log("Migration failed!");
Logger.Log(t.Exception.ToString(), LoggingTarget.Database);
notificationOverlay.Post(new SimpleErrorNotification
{
Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).",
Activated = () =>
{
game.OpenUrlExternally($@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true);
const string attachment_filename = "attach_me.zip";
const string backup_folder = "backups";
var backupStorage = storage.GetStorageForDirectory(backup_folder);
backupStorage.Delete(attachment_filename);
try
{
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty));
zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate));
}
}
catch { }
backupStorage.PresentFileExternally(attachment_filename);
return true;
}
});
}
// Regardless of success, since the game is going to continue with startup let's move the ef database out of the way.
// If we were to not do this, the migration would run another time the next time the user starts the game.
deletePreRealmData();
migrationCompleted.SetResult(true); migrationCompleted.SetResult(true);
efContextFactory.SetMigrationCompletion(); efContextFactory.SetMigrationCompletion();
}); });
} }
private void deletePreRealmData()
{
// Delete the database permanently.
// Will cause future startups to not attempt migration.
efContextFactory.ResetDatabase();
}
private void log(string message) private void log(string message)
{ {
Logger.Log(message, LoggingTarget.Database); Logger.Log(message, LoggingTarget.Database);

View File

@ -27,9 +27,9 @@ namespace osu.Game.Online.Chat
externalLinkWarning = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning); externalLinkWarning = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning);
} }
public void OpenUrlExternally(string url) public void OpenUrlExternally(string url, bool bypassWarning = false)
{ {
if (externalLinkWarning.Value) if (!bypassWarning && externalLinkWarning.Value)
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url)));
else else
host.OpenUrlExternally(url); host.OpenUrlExternally(url);

View File

@ -357,12 +357,12 @@ namespace osu.Game
} }
}); });
public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ =>
{ {
if (url.StartsWith('/')) if (url.StartsWith('/'))
url = $"{API.APIEndpointUrl}{url}"; url = $"{API.APIEndpointUrl}{url}";
externalLinkOpener.OpenUrlExternally(url); externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning);
}); });
/// <summary> /// <summary>

View File

@ -202,16 +202,18 @@ namespace osu.Game
// See https://github.com/ppy/osu/pull/16547 for more discussion. // See https://github.com/ppy/osu/pull/16547 for more discussion.
if (EFContextFactory != null) if (EFContextFactory != null)
{ {
const string backup_folder = "backups";
string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
EFContextFactory.CreateBackup($"client.{migration}.db"); EFContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.db"));
realm.CreateBackup($"client.{migration}.realm"); realm.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.realm"));
using (var source = Storage.GetStream("collection.db")) using (var source = Storage.GetStream("collection.db"))
{ {
if (source != null) if (source != null)
{ {
using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew))
source.CopyTo(destination); source.CopyTo(destination);
} }
} }

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
public abstract class Ruleset public abstract class Ruleset
{ {
public RulesetInfo RulesetInfo { get; internal set; } public RulesetInfo RulesetInfo { get; }
private static readonly ConcurrentDictionary<string, IMod[]> mod_reference_cache = new ConcurrentDictionary<string, IMod[]>(); private static readonly ConcurrentDictionary<string, IMod[]> mod_reference_cache = new ConcurrentDictionary<string, IMod[]>();

View File

@ -37,14 +37,6 @@ namespace osu.Game.Rulesets
{ {
} }
public RulesetInfo(int? onlineID, string name, string shortName, bool available)
{
OnlineID = onlineID ?? -1;
Name = name;
ShortName = shortName;
Available = available;
}
public bool Available { get; set; } public bool Available { get; set; }
public bool Equals(RulesetInfo? other) public bool Equals(RulesetInfo? other)

View File

@ -574,7 +574,9 @@ namespace osu.Game.Screens.Edit
// To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend.
// This is required as the editor makes its local changes via EditorBeatmap // This is required as the editor makes its local changes via EditorBeatmap
// (which are not propagated outwards to a potentially cached WorkingBeatmap). // (which are not propagated outwards to a potentially cached WorkingBeatmap).
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); ((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo);
var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID);
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo);
if (!(refetchedBeatmap is DummyWorkingBeatmap)) if (!(refetchedBeatmap is DummyWorkingBeatmap))
{ {