diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index a7078f1c09..3c6aaa39ca 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index c133b0e3f8..0719dd30df 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index 92b48470e8..d0db43cc81 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index c133b0e3f8..0719dd30df 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index a8599f2cb6..57b914bee6 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 23f6222eb6..f078a4353d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
Artist = @"Unknown",
Title = @"You're breathtaking",
- AuthorString = @"Everyone",
+ Author = { Username = @"Everyone" },
},
Ruleset = new CatchRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index d5496b7479..13f2e25f05 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index 2f12b1535e..d51a6da4f9 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index e5b2e070d8..fea2e408f6 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
index 4bdb85ba60..54bc6f1912 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
- AuthorString = @"peppy",
+ Author = { Username = @"peppy" },
},
Ruleset = new TaikoRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
index b976735223..e73fb6bb1f 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
Artist = "Unknown",
Title = "Sample Beatmap",
- AuthorString = "Craftplacer",
+ Author = { Username = "Craftplacer" },
},
Ruleset = new TaikoRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index e48d80323a..ad3713e047 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 3d78043c73..7ac26699a5 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
ArtistUnicode = "check unicode too",
Title = "Title goes here",
TitleUnicode = "Title goes here",
- AuthorString = "The Author",
+ Author = { Username = "The Author" },
Source = "unit tests",
Tags = "look for tags too",
},
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 1d99a5c20d..e1da31abcb 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Resources
// Create random metadata, then we can check if sorting works based on these
Artist = "Some Artist " + RNG.Next(0, 9),
Title = $"Some Song (set id {setId}) {Guid.NewGuid()}",
- AuthorString = "Some Guy " + RNG.Next(0, 9),
+ Author = { Username = "Some Guy " + RNG.Next(0, 9) },
};
var beatmapSet = new BeatmapSetInfo
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index 181b0c71f2..ae7705b97d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Artist = "Some Artist",
Title = "Some Beatmap",
- AuthorString = "Some Author"
+ Author = { Username = "Some Author" },
};
var beatmapSetInfo = new BeatmapSetInfo
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
index 69b30ec6a0..1b82094603 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Navigation
var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
- AuthorString = "SomeAuthor",
+ Author = { Username = "SomeAuthor" },
Title = $"import {i}"
};
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
index f9649db135..b8e49d155e 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Navigation
Metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
- AuthorString = "SomeAuthor",
+ Author = { Username = "SomeAuthor" },
Title = "import"
},
BaseDifficulty = new BeatmapDifficulty(),
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation
Metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
- AuthorString = "SomeAuthor",
+ Author = { Username = "SomeAuthor" },
Title = "import"
},
BaseDifficulty = new BeatmapDifficulty(),
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs
index e52f823f0b..63bd7c8068 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsScreen.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Playlists
{
@@ -12,9 +10,6 @@ namespace osu.Game.Tests.Visual.Playlists
{
protected override bool UseOnlineAPI => true;
- [Cached]
- private MusicController musicController { get; set; } = new MusicController();
-
public TestScenePlaylistsScreen()
{
var multi = new Screens.OnlinePlay.Playlists.Playlists();
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 0f5bea10e8..0298c3bea9 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -409,7 +409,7 @@ namespace osu.Game.Tests.Visual.SongSelect
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
if (i == 16)
- set.Beatmaps.ForEach(b => b.Metadata.AuthorString = zzz_string);
+ set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string);
sets.Add(set);
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
index 9ad5242df4..efacc395f1 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
@@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
Metadata = new BeatmapMetadata
{
- AuthorString = $"{ruleset.ShortName}Author",
+ Author = { Username = $"{ruleset.ShortName}Author" },
Artist = $"{ruleset.ShortName}Artist",
Source = $"{ruleset.ShortName}Source",
Title = $"{ruleset.ShortName}Title"
@@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
Metadata = new BeatmapMetadata
{
- AuthorString = "WWWWWWWWWWWWWWW",
+ Author = { Username = "WWWWWWWWWWWWWWW" },
Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist",
Source = "Verrrrry long Source",
Title = "Verrrrry long Title"
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index c64ef918e3..3b115d43e5 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index fb09a7be1e..130fcfaca1 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -7,7 +7,7 @@
-
+
WinExe
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index ccecd69d21..a9eb7da5c9 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
{
Artist = @"Unknown",
Title = @"Unknown",
- AuthorString = @"Unknown Creator",
+ Author = { Username = @"Unknown Creator" },
},
DifficultyName = @"Normal",
BaseDifficulty = Difficulty,
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index b0e10c2c38..9ad9e9b1c9 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -64,6 +64,7 @@ namespace osu.Game.Beatmaps
[Ignored]
public RealmNamedFileUsage? File => BeatmapSet?.Files.FirstOrDefault(f => f.File.Hash == Hash);
+ [Ignored]
public BeatmapOnlineStatus Status
{
get => (BeatmapOnlineStatus)StatusInt;
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 6349476550..a3385e3abe 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -45,16 +45,6 @@ namespace osu.Game.Beatmaps
IUser IBeatmapMetadataInfo.Author => Author;
- #region Compatibility properties
-
- public string AuthorString
- {
- get => Author.Username;
- set => Author.Username = value;
- }
-
- #endregion
-
public override string ToString() => this.GetDisplayTitle();
}
}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index f6ca184ea3..a934d1a2e3 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -32,6 +32,7 @@ namespace osu.Game.Beatmaps
public IList Files { get; } = null!;
+ [Ignored]
public BeatmapOnlineStatus Status
{
get => (BeatmapOnlineStatus)StatusInt;
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index e5db9d045a..893eb8ab78 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Formats
break;
case @"Creator":
- metadata.AuthorString = pair.Value;
+ metadata.Author.Username = pair.Value;
break;
case @"Version":
diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs
index f79505d7c5..635c4373cd 100644
--- a/osu.Game/Database/DatabaseContextFactory.cs
+++ b/osu.Game/Database/DatabaseContextFactory.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.EntityFrameworkCore.Storage;
+using osu.Framework.Development;
+using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Statistics;
@@ -144,6 +147,18 @@ namespace osu.Game.Database
Database = { AutoTransactionsEnabled = false }
};
+ public void CreateBackup(string backupFilename)
+ {
+ Logger.Log($"Creating full EF database backup at {backupFilename}", LoggingTarget.Database);
+
+ 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);
+
+ using (var source = storage.GetStream(DATABASE_NAME))
+ using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew))
+ source.CopyTo(destination);
+ }
+
public void ResetDatabase()
{
lock (writeLock)
diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs
index 7683accc5c..bbf7c27320 100644
--- a/osu.Game/Database/EFToRealmMigrator.cs
+++ b/osu.Game/Database/EFToRealmMigrator.cs
@@ -1,8 +1,11 @@
// Copyright (c) ppy Pty Ltd . 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.Linq;
using Microsoft.EntityFrameworkCore;
+using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Models;
@@ -21,6 +24,8 @@ namespace osu.Game.Database
private readonly RealmContextFactory realmContextFactory;
private readonly OsuConfigManager config;
+ private bool hasTakenBackup;
+
public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config)
{
this.efContextFactory = efContextFactory;
@@ -30,97 +35,125 @@ namespace osu.Game.Database
public void Run()
{
- using (var db = efContextFactory.GetForWrite())
+ using (var ef = efContextFactory.GetForWrite())
{
- migrateSettings(db);
- migrateSkins(db);
-
- migrateBeatmaps(db);
- migrateScores(db);
+ migrateSettings(ef);
+ migrateSkins(ef);
+ migrateBeatmaps(ef);
+ migrateScores(ef);
}
+
+ // Delete the database permanently.
+ // Will cause future startups to not attempt migration.
+ Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database);
+ efContextFactory.ResetDatabase();
}
- private void migrateBeatmaps(DatabaseWriteUsage db)
+ private void migrateBeatmaps(DatabaseWriteUsage ef)
{
// can be removed 20220730.
- var existingBeatmapSets = db.Context.EFBeatmapSetInfo
- .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo)
- .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
- .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
- .Include(s => s.Files).ThenInclude(f => f.FileInfo)
- .Include(s => s.Metadata)
- .ToList();
+ List existingBeatmapSets = ef.Context.EFBeatmapSetInfo
+ .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Files).ThenInclude(f => f.FileInfo)
+ .Include(s => s.Metadata)
+ .ToList();
+
+ Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database);
// previous entries in EF are removed post migration.
if (!existingBeatmapSets.Any())
+ {
+ Logger.Log("No beatmaps found to migrate", LoggingTarget.Database);
return;
+ }
using (var realm = realmContextFactory.CreateContext())
- using (var transaction = realm.BeginWrite())
{
- // only migrate data if the realm database is empty.
- // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`.
- if (!realm.All().Any(s => !s.Protected))
+ Logger.Log($"Found {existingBeatmapSets.Count} beatmaps in EF", LoggingTarget.Database);
+
+ if (!hasTakenBackup)
{
- foreach (var beatmapSet in existingBeatmapSets)
+ string migration = $"before_beatmap_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
+
+ efContextFactory.CreateBackup($"client.{migration}.db");
+ realmContextFactory.CreateBackup($"client.{migration}.realm");
+
+ hasTakenBackup = true;
+ }
+
+ // only migrate data if the realm database is empty.
+ // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`.
+ if (realm.All().Any(s => !s.Protected))
+ {
+ Logger.Log("Skipping migration as realm already has beatmaps loaded", LoggingTarget.Database);
+ }
+ else
+ {
+ using (var transaction = realm.BeginWrite())
{
- var realmBeatmapSet = new BeatmapSetInfo
+ foreach (var beatmapSet in existingBeatmapSets)
{
- OnlineID = beatmapSet.OnlineID ?? -1,
- DateAdded = beatmapSet.DateAdded,
- Status = beatmapSet.Status,
- DeletePending = beatmapSet.DeletePending,
- Hash = beatmapSet.Hash,
- Protected = beatmapSet.Protected,
- };
-
- migrateFiles(beatmapSet, realm, realmBeatmapSet);
-
- foreach (var beatmap in beatmapSet.Beatmaps)
- {
- var realmBeatmap = new BeatmapInfo
+ var realmBeatmapSet = new BeatmapSetInfo
{
- DifficultyName = beatmap.DifficultyName,
- Status = beatmap.Status,
- OnlineID = beatmap.OnlineID ?? -1,
- Length = beatmap.Length,
- BPM = beatmap.BPM,
- Hash = beatmap.Hash,
- StarRating = beatmap.StarRating,
- MD5Hash = beatmap.MD5Hash,
- Hidden = beatmap.Hidden,
- AudioLeadIn = beatmap.AudioLeadIn,
- StackLeniency = beatmap.StackLeniency,
- SpecialStyle = beatmap.SpecialStyle,
- LetterboxInBreaks = beatmap.LetterboxInBreaks,
- WidescreenStoryboard = beatmap.WidescreenStoryboard,
- EpilepsyWarning = beatmap.EpilepsyWarning,
- SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate,
- DistanceSpacing = beatmap.DistanceSpacing,
- BeatDivisor = beatmap.BeatDivisor,
- GridSize = beatmap.GridSize,
- TimelineZoom = beatmap.TimelineZoom,
- Countdown = beatmap.Countdown,
- CountdownOffset = beatmap.CountdownOffset,
- MaxCombo = beatmap.MaxCombo,
- Bookmarks = beatmap.Bookmarks,
- Ruleset = realm.Find(beatmap.RulesetInfo.ShortName),
- Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty),
- Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata),
- BeatmapSet = realmBeatmapSet,
+ OnlineID = beatmapSet.OnlineID ?? -1,
+ DateAdded = beatmapSet.DateAdded,
+ Status = beatmapSet.Status,
+ DeletePending = beatmapSet.DeletePending,
+ Hash = beatmapSet.Hash,
+ Protected = beatmapSet.Protected,
};
- realmBeatmapSet.Beatmaps.Add(realmBeatmap);
+ migrateFiles(beatmapSet, realm, realmBeatmapSet);
+
+ foreach (var beatmap in beatmapSet.Beatmaps)
+ {
+ var realmBeatmap = new BeatmapInfo
+ {
+ DifficultyName = beatmap.DifficultyName,
+ Status = beatmap.Status,
+ OnlineID = beatmap.OnlineID ?? -1,
+ Length = beatmap.Length,
+ BPM = beatmap.BPM,
+ Hash = beatmap.Hash,
+ StarRating = beatmap.StarRating,
+ MD5Hash = beatmap.MD5Hash,
+ Hidden = beatmap.Hidden,
+ AudioLeadIn = beatmap.AudioLeadIn,
+ StackLeniency = beatmap.StackLeniency,
+ SpecialStyle = beatmap.SpecialStyle,
+ LetterboxInBreaks = beatmap.LetterboxInBreaks,
+ WidescreenStoryboard = beatmap.WidescreenStoryboard,
+ EpilepsyWarning = beatmap.EpilepsyWarning,
+ SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate,
+ DistanceSpacing = beatmap.DistanceSpacing,
+ BeatDivisor = beatmap.BeatDivisor,
+ GridSize = beatmap.GridSize,
+ TimelineZoom = beatmap.TimelineZoom,
+ Countdown = beatmap.Countdown,
+ CountdownOffset = beatmap.CountdownOffset,
+ MaxCombo = beatmap.MaxCombo,
+ Bookmarks = beatmap.Bookmarks,
+ Ruleset = realm.Find(beatmap.RulesetInfo.ShortName),
+ Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty),
+ Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata),
+ BeatmapSet = realmBeatmapSet,
+ };
+
+ realmBeatmapSet.Beatmaps.Add(realmBeatmap);
+ }
+
+ realm.Add(realmBeatmapSet);
}
- realm.Add(realmBeatmapSet);
+ transaction.Commit();
+ Logger.Log($"Successfully migrated {existingBeatmapSets.Count} beatmaps to realm", LoggingTarget.Database);
}
}
- db.Context.RemoveRange(existingBeatmapSets);
+ ef.Context.RemoveRange(existingBeatmapSets);
// Intentionally don't clean up the files, so they don't get purged by EF.
-
- transaction.Commit();
}
}
@@ -144,69 +177,91 @@ namespace osu.Game.Database
PreviewTime = metadata.PreviewTime,
AudioFile = metadata.AudioFile,
BackgroundFile = metadata.BackgroundFile,
- AuthorString = metadata.AuthorString,
};
}
private void migrateScores(DatabaseWriteUsage db)
{
// can be removed 20220730.
- var existingScores = db.Context.ScoreInfo
- .Include(s => s.Ruleset)
- .Include(s => s.BeatmapInfo)
- .Include(s => s.Files)
- .ThenInclude(f => f.FileInfo)
- .ToList();
+ List existingScores = db.Context.ScoreInfo
+ .Include(s => s.Ruleset)
+ .Include(s => s.BeatmapInfo)
+ .Include(s => s.Files)
+ .ThenInclude(f => f.FileInfo)
+ .ToList();
+
+ Logger.Log("Beginning scores migration to realm", LoggingTarget.Database);
// previous entries in EF are removed post migration.
if (!existingScores.Any())
+ {
+ Logger.Log("No scores found to migrate", LoggingTarget.Database);
return;
+ }
using (var realm = realmContextFactory.CreateContext())
- using (var transaction = realm.BeginWrite())
{
- // only migrate data if the realm database is empty.
- // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`.
- if (!realm.All().Any())
+ Logger.Log($"Found {existingScores.Count} scores in EF", LoggingTarget.Database);
+
+ if (!hasTakenBackup)
{
- foreach (var score in existingScores)
+ string migration = $"before_score_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
+
+ efContextFactory.CreateBackup($"client.{migration}.db");
+ realmContextFactory.CreateBackup($"client.{migration}.realm");
+
+ hasTakenBackup = true;
+ }
+
+ // only migrate data if the realm database is empty.
+ if (realm.All().Any())
+ {
+ Logger.Log("Skipping migration as realm already has scores loaded", LoggingTarget.Database);
+ }
+ else
+ {
+ using (var transaction = realm.BeginWrite())
{
- var realmScore = new ScoreInfo
+ foreach (var score in existingScores)
{
- Hash = score.Hash,
- DeletePending = score.DeletePending,
- OnlineID = score.OnlineID ?? -1,
- ModsJson = score.ModsJson,
- StatisticsJson = score.StatisticsJson,
- User = score.User,
- TotalScore = score.TotalScore,
- MaxCombo = score.MaxCombo,
- Accuracy = score.Accuracy,
- HasReplay = ((IScoreInfo)score).HasReplay,
- Date = score.Date,
- PP = score.PP,
- BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash),
- Ruleset = realm.Find(score.Ruleset.ShortName),
- Rank = score.Rank,
- HitEvents = score.HitEvents,
- Passed = score.Passed,
- Combo = score.Combo,
- Position = score.Position,
- Statistics = score.Statistics,
- Mods = score.Mods,
- APIMods = score.APIMods,
- };
+ var realmScore = new ScoreInfo
+ {
+ Hash = score.Hash,
+ DeletePending = score.DeletePending,
+ OnlineID = score.OnlineID ?? -1,
+ ModsJson = score.ModsJson,
+ StatisticsJson = score.StatisticsJson,
+ User = score.User,
+ TotalScore = score.TotalScore,
+ MaxCombo = score.MaxCombo,
+ Accuracy = score.Accuracy,
+ HasReplay = ((IScoreInfo)score).HasReplay,
+ Date = score.Date,
+ PP = score.PP,
+ BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash),
+ Ruleset = realm.Find(score.Ruleset.ShortName),
+ Rank = score.Rank,
+ HitEvents = score.HitEvents,
+ Passed = score.Passed,
+ Combo = score.Combo,
+ Position = score.Position,
+ Statistics = score.Statistics,
+ Mods = score.Mods,
+ APIMods = score.APIMods,
+ };
- migrateFiles(score, realm, realmScore);
+ migrateFiles(score, realm, realmScore);
- realm.Add(realmScore);
+ realm.Add(realmScore);
+ }
+
+ transaction.Commit();
+ Logger.Log($"Successfully migrated {existingScores.Count} scores to realm", LoggingTarget.Database);
}
}
db.Context.RemoveRange(existingScores);
// Intentionally don't clean up the files, so they don't get purged by EF.
-
- transaction.Commit();
}
}
@@ -243,6 +298,8 @@ namespace osu.Game.Database
// note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`.
if (!realm.All().Any(s => !s.Protected))
{
+ Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database);
+
foreach (var skin in existingSkins)
{
var realmSkin = new SkinInfo
@@ -286,18 +343,22 @@ namespace osu.Game.Database
private void migrateSettings(DatabaseWriteUsage db)
{
// migrate ruleset settings. can be removed 20220315.
- var existingSettings = db.Context.DatabasedSetting;
+ var existingSettings = db.Context.DatabasedSetting.ToList();
// previous entries in EF are removed post migration.
if (!existingSettings.Any())
return;
+ Logger.Log("Beginning settings migration to realm", LoggingTarget.Database);
+
using (var realm = realmContextFactory.CreateContext())
using (var transaction = realm.BeginWrite())
{
// only migrate data if the realm database is empty.
if (!realm.All().Any())
{
+ Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database);
+
foreach (var dkb in existingSettings)
{
if (dkb.RulesetID == null)
diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs
index b9c0d1b681..8be8eab567 100644
--- a/osu.Game/Database/RealmContextFactory.cs
+++ b/osu.Game/Database/RealmContextFactory.cs
@@ -361,6 +361,17 @@ namespace osu.Game.Database
private string? getRulesetShortNameFromLegacyID(long rulesetId) =>
efContextFactory?.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName;
+ public void CreateBackup(string backupFilename)
+ {
+ using (BlockAllOperations())
+ {
+ Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database);
+ using (var source = storage.GetStream(Filename))
+ using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew))
+ source.CopyTo(destination);
+ }
+ }
+
///
/// Flush any active contexts and block any further writes.
///
@@ -374,17 +385,17 @@ namespace osu.Game.Database
if (isDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory));
- if (!ThreadSafety.IsUpdateThread)
- throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
-
- Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
-
try
{
contextCreationLock.Wait();
lock (contextLock)
{
+ if (!ThreadSafety.IsUpdateThread && context != null)
+ throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
+
+ Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
+
context?.Dispose();
context = null;
}
diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs
index 140f41c5d8..746a43fd37 100644
--- a/osu.Game/Database/RealmObjectExtensions.cs
+++ b/osu.Game/Database/RealmObjectExtensions.cs
@@ -119,6 +119,7 @@ namespace osu.Game.Database
c.CreateMap()
.MaxDepth(1)
+ // This is not required as it will be populated in the `AfterMap` call from the `BeatmapInfo`'s parent.
.ForMember(b => b.BeatmapSet, cc => cc.Ignore());
}).CreateMapper();
diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs
index 6a408847fe..32813ada16 100644
--- a/osu.Game/Input/Bindings/RealmKeyBinding.cs
+++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs
@@ -20,12 +20,14 @@ namespace osu.Game.Input.Bindings
public int? Variant { get; set; }
+ [Ignored]
public KeyCombination KeyCombination
{
get => KeyCombinationString;
set => KeyCombinationString = value.ToString();
}
+ [Ignored]
public object Action
{
get => ActionInt;
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 2f9e9d5734..b24fdf2bfe 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -193,6 +193,7 @@ namespace osu.Game
dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage));
dependencies.CacheAs(RulesetStore);
+ // A non-null context factory means there's still content to migrate.
if (efContextFactory != null)
new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig).Run();
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index e1328d8a06..b268e2cd91 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -104,6 +104,7 @@ namespace osu.Game.Scoring
}
}
+ [Ignored]
public ScoreRank Rank
{
get => (ScoreRank)RankInt;
diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs
index 0d2b093a2e..f0ca3e1bbc 100644
--- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs
+++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs
@@ -110,7 +110,7 @@ namespace osu.Game.Screens.Edit.Setup
Beatmap.Metadata.TitleUnicode = TitleTextBox.Current.Value;
Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value;
- Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value;
+ Beatmap.Metadata.Author.Username = creatorTextBox.Current.Value;
Beatmap.BeatmapInfo.DifficultyName = difficultyTextBox.Current.Value;
Beatmap.Metadata.Source = sourceTextBox.Current.Value;
Beatmap.Metadata.Tags = tagsTextBox.Current.Value;