2021-11-30 14:41:18 +08:00
// 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.
2022-03-28 16:33:15 +08:00
using System ;
2022-01-27 13:52:43 +08:00
using System.IO ;
2021-11-30 14:41:18 +08:00
using System.Linq ;
2022-01-21 13:56:49 +08:00
using System.Threading.Tasks ;
2021-11-30 14:41:18 +08:00
using Microsoft.EntityFrameworkCore ;
2022-03-28 22:46:04 +08:00
using osu.Framework ;
2022-01-21 13:56:49 +08:00
using osu.Framework.Allocation ;
2022-01-22 21:20:28 +08:00
using osu.Framework.Development ;
2022-01-21 13:56:49 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2022-01-18 13:19:31 +08:00
using osu.Framework.Logging ;
2022-01-27 13:52:43 +08:00
using osu.Framework.Platform ;
2022-01-13 16:51:23 +08:00
using osu.Game.Beatmaps ;
2021-11-30 14:41:18 +08:00
using osu.Game.Configuration ;
2022-01-21 13:56:49 +08:00
using osu.Game.Graphics ;
2022-03-28 16:33:15 +08:00
using osu.Game.Graphics.Containers ;
2022-01-21 13:56:49 +08:00
using osu.Game.Graphics.Sprites ;
using osu.Game.Graphics.UserInterface ;
2021-11-30 14:41:18 +08:00
using osu.Game.Models ;
2022-01-27 13:33:44 +08:00
using osu.Game.Overlays ;
using osu.Game.Overlays.Notifications ;
2022-01-13 16:51:23 +08:00
using osu.Game.Rulesets ;
using osu.Game.Scoring ;
2021-11-30 14:41:18 +08:00
using osu.Game.Skinning ;
2022-01-21 13:56:49 +08:00
using osuTK ;
2022-01-13 16:51:23 +08:00
using Realms ;
2022-01-27 13:52:43 +08:00
using SharpCompress.Archives ;
using SharpCompress.Archives.Zip ;
using SharpCompress.Common ;
using SharpCompress.Writers.Zip ;
2021-11-30 14:41:18 +08:00
namespace osu.Game.Database
{
2022-01-21 13:56:49 +08:00
internal class EFToRealmMigrator : CompositeDrawable
2021-11-30 14:41:18 +08:00
{
2022-01-24 17:55:15 +08:00
public Task < bool > MigrationCompleted = > migrationCompleted . Task ;
private readonly TaskCompletionSource < bool > migrationCompleted = new TaskCompletionSource < bool > ( ) ;
2021-11-30 14:41:18 +08:00
2022-01-21 13:56:49 +08:00
[Resolved]
private DatabaseContextFactory efContextFactory { get ; set ; } = null ! ;
[Resolved]
2022-01-24 18:59:58 +08:00
private RealmAccess realm { get ; set ; } = null ! ;
2022-01-21 13:56:49 +08:00
[Resolved]
private OsuConfigManager config { get ; set ; } = null ! ;
2022-01-27 13:33:44 +08:00
[Resolved]
2022-04-18 18:59:57 +08:00
private INotificationOverlay notificationOverlay { get ; set ; } = null ! ;
2022-01-27 13:33:44 +08:00
[Resolved]
private OsuGame game { get ; set ; } = null ! ;
2022-01-27 13:52:43 +08:00
[Resolved]
private Storage storage { get ; set ; } = null ! ;
2022-03-28 16:33:15 +08:00
private readonly OsuTextFlowContainer currentOperationText ;
2022-01-21 13:56:49 +08:00
public EFToRealmMigrator ( )
2021-11-30 14:41:18 +08:00
{
2022-01-21 13:56:49 +08:00
RelativeSizeAxes = Axes . Both ;
InternalChildren = new Drawable [ ]
{
new FillFlowContainer
{
AutoSizeAxes = Axes . Both ,
Direction = FillDirection . Vertical ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Spacing = new Vector2 ( 10 ) ,
Children = new Drawable [ ]
{
new OsuSpriteText
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Text = "Database migration in progress" ,
Font = OsuFont . Default . With ( size : 40 )
} ,
new OsuSpriteText
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Text = "This could take a few minutes depending on the speed of your disk(s)." ,
Font = OsuFont . Default . With ( size : 30 )
} ,
new OsuSpriteText
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Text = "Please keep the window open until this completes!" ,
Font = OsuFont . Default . With ( size : 30 )
} ,
new LoadingSpinner ( true )
{
State = { Value = Visibility . Visible }
} ,
2022-03-28 16:33:15 +08:00
currentOperationText = new OsuTextFlowContainer ( cp = > cp . Font = OsuFont . Default . With ( size : 30 ) )
2022-01-21 13:56:49 +08:00
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2022-03-28 16:33:15 +08:00
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
TextAnchor = Anchor . TopCentre ,
2022-01-21 13:56:49 +08:00
} ,
}
} ,
} ;
2021-11-30 14:41:18 +08:00
}
2022-01-21 13:56:49 +08:00
protected override void LoadComplete ( )
2021-11-30 14:41:18 +08:00
{
2022-01-21 13:56:49 +08:00
base . LoadComplete ( ) ;
2022-01-27 13:33:44 +08:00
beginMigration ( ) ;
}
2022-01-21 13:56:49 +08:00
2022-01-27 13:33:44 +08:00
private void beginMigration ( )
{
2022-06-16 15:50:00 +08:00
const string backup_folder = "backups" ;
string backupSuffix = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}" ;
// required for initial backup.
var realmBlockOperations = realm . BlockAllOperations ( ) ;
2022-01-21 13:56:49 +08:00
Task . Factory . StartNew ( ( ) = >
2022-01-18 19:47:53 +08:00
{
2022-06-16 22:37:24 +08:00
try
{
realm . CreateBackup ( Path . Combine ( backup_folder , $"client.{backupSuffix}.realm" ) , realmBlockOperations ) ;
}
finally
{
// Above call will dispose of the blocking token when done.
// Clean up here so we don't accidentally dispose twice.
realmBlockOperations = null ;
}
2022-06-16 22:29:33 +08:00
2022-06-16 15:50:00 +08:00
efContextFactory . CreateBackup ( Path . Combine ( backup_folder , $"client.{backupSuffix}.db" ) ) ;
2022-01-21 13:56:49 +08:00
using ( var ef = efContextFactory . Get ( ) )
{
2022-01-25 12:09:47 +08:00
realm . Write ( r = >
2022-01-24 17:55:15 +08:00
{
// Before beginning, ensure realm is in an empty state.
// Migrations which are half-completed could lead to issues if the user tries a second time.
// Note that we only do this for beatmaps and scores since the other migrations are yonks old.
2022-01-25 12:09:47 +08:00
r . RemoveAll < BeatmapSetInfo > ( ) ;
r . RemoveAll < BeatmapInfo > ( ) ;
r . RemoveAll < BeatmapMetadata > ( ) ;
r . RemoveAll < ScoreInfo > ( ) ;
2022-01-24 17:55:15 +08:00
} ) ;
2022-01-29 21:42:34 +08:00
ef . Migrate ( ) ;
2022-01-21 13:56:49 +08:00
migrateSettings ( ef ) ;
migrateSkins ( ef ) ;
migrateBeatmaps ( ef ) ;
migrateScores ( ef ) ;
}
} , TaskCreationOptions . LongRunning ) . ContinueWith ( t = >
{
2022-01-27 13:33:44 +08:00
if ( t . Exception = = null )
{
log ( "Migration successful!" ) ;
if ( DebugUtils . IsDebugBuild )
2022-03-28 16:33:15 +08:00
{
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 ) ;
}
2022-01-27 13:33:44 +08:00
}
else
{
log ( "Migration failed!" ) ;
Logger . Log ( t . Exception . ToString ( ) , LoggingTarget . Database ) ;
2022-01-27 13:34:18 +08:00
2022-03-28 22:46:04 +08:00
if ( RuntimeInfo . OS = = RuntimeInfo . Platform . macOS & & t . Exception . Flatten ( ) . InnerException is TypeInitializationException )
2022-03-28 16:33:15 +08:00
{
// Not guaranteed to be the only cause of exception, but let's roll with it for now.
log ( "Please download and run the intel version of osu! once\nto allow data migration to complete!" ) ;
2022-03-28 22:59:11 +08:00
efContextFactory . SetMigrationCompletion ( ) ;
2022-03-28 16:33:15 +08:00
return ;
}
2022-01-27 13:34:18 +08:00
notificationOverlay . Post ( new SimpleErrorNotification
{
2022-03-28 16:33:15 +08:00
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)." ,
2022-01-27 13:34:18 +08:00
Activated = ( ) = >
{
2022-03-28 16:33:15 +08:00
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 ) ;
2022-01-27 13:52:43 +08:00
const string attachment_filename = "attach_me.zip" ;
var backupStorage = storage . GetStorageForDirectory ( backup_folder ) ;
backupStorage . Delete ( attachment_filename ) ;
2022-01-27 13:55:52 +08:00
try
2022-01-27 13:52:43 +08:00
{
2022-01-27 13:55:52 +08:00
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 ) ) ;
}
2022-01-27 13:52:43 +08:00
}
2022-01-27 13:55:52 +08:00
catch { }
2022-01-27 13:52:43 +08:00
backupStorage . PresentFileExternally ( attachment_filename ) ;
2022-01-27 13:34:18 +08:00
return true ;
}
} ) ;
2022-01-27 13:33:44 +08:00
}
// 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 ( ) ;
2022-06-16 22:31:38 +08:00
// If something went wrong and the disposal token wasn't invoked above, ensure it is here.
2022-06-16 22:29:33 +08:00
realmBlockOperations ? . Dispose ( ) ;
2022-06-16 15:50:00 +08:00
2022-01-24 17:55:15 +08:00
migrationCompleted . SetResult ( true ) ;
2022-01-26 23:34:51 +08:00
efContextFactory . SetMigrationCompletion ( ) ;
2022-01-21 13:56:49 +08:00
} ) ;
}
2022-01-18 13:17:43 +08:00
2022-01-27 13:33:44 +08:00
private void deletePreRealmData ( )
{
// Delete the database permanently.
// Will cause future startups to not attempt migration.
efContextFactory . ResetDatabase ( ) ;
}
2022-01-21 13:56:49 +08:00
private void log ( string message )
{
Logger . Log ( message , LoggingTarget . Database ) ;
Scheduler . AddOnce ( m = > currentOperationText . Text = m , message ) ;
2022-01-13 16:51:23 +08:00
}
2022-01-19 14:52:59 +08:00
private void migrateBeatmaps ( OsuDbContext ef )
2022-01-13 17:02:08 +08:00
{
// can be removed 20220730.
2022-01-19 14:52:59 +08:00
var existingBeatmapSets = ef . EFBeatmapSetInfo
2022-01-18 18:12:10 +08:00
. 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 )
2022-02-15 14:23:14 +08:00
. Include ( s = > s . Metadata )
. AsSplitQuery ( ) ;
2022-01-13 17:02:08 +08:00
2022-01-21 13:56:49 +08:00
log ( "Beginning beatmaps migration to realm" ) ;
2022-01-18 13:41:02 +08:00
2022-01-13 17:02:08 +08:00
// previous entries in EF are removed post migration.
if ( ! existingBeatmapSets . Any ( ) )
2022-01-18 13:41:02 +08:00
{
2022-01-21 13:56:49 +08:00
log ( "No beatmaps found to migrate" ) ;
2022-01-13 17:02:08 +08:00
return ;
2022-01-18 13:41:02 +08:00
}
2022-01-18 13:19:31 +08:00
2022-01-18 18:12:10 +08:00
int count = existingBeatmapSets . Count ( ) ;
2022-01-25 12:04:05 +08:00
realm . Run ( r = >
2022-01-13 17:02:08 +08:00
{
2022-01-21 13:56:49 +08:00
log ( $"Found {count} beatmaps in EF" ) ;
2022-01-19 09:16:54 +08:00
2022-01-25 12:04:05 +08:00
var transaction = r . BeginWrite ( ) ;
2022-01-24 17:55:15 +08:00
int written = 0 ;
2022-01-27 23:14:18 +08:00
int missing = 0 ;
2022-01-19 15:01:17 +08:00
2022-01-24 17:55:15 +08:00
try
{
foreach ( var beatmapSet in existingBeatmapSets )
2022-01-13 17:02:08 +08:00
{
2022-01-24 17:55:15 +08:00
if ( + + written % 1000 = = 0 )
2022-01-13 17:02:08 +08:00
{
2022-01-24 17:55:15 +08:00
transaction . Commit ( ) ;
2022-01-25 12:04:05 +08:00
transaction = r . BeginWrite ( ) ;
2022-01-24 17:55:15 +08:00
log ( $"Migrated {written}/{count} beatmaps..." ) ;
}
2022-01-19 15:01:17 +08:00
2022-01-24 17:55:15 +08:00
var realmBeatmapSet = new BeatmapSetInfo
{
OnlineID = beatmapSet . OnlineID ? ? - 1 ,
DateAdded = beatmapSet . DateAdded ,
Status = beatmapSet . Status ,
DeletePending = beatmapSet . DeletePending ,
Hash = beatmapSet . Hash ,
Protected = beatmapSet . Protected ,
} ;
2022-01-25 12:04:05 +08:00
migrateFiles ( beatmapSet , r , realmBeatmapSet ) ;
2022-01-13 17:02:08 +08:00
2022-01-24 17:55:15 +08:00
foreach ( var beatmap in beatmapSet . Beatmaps )
{
2022-01-25 12:04:05 +08:00
var ruleset = r . Find < RulesetInfo > ( beatmap . RulesetInfo . ShortName ) ;
2022-01-24 17:55:15 +08:00
var metadata = getBestMetadata ( beatmap . Metadata , beatmapSet . Metadata ) ;
2022-01-18 13:41:02 +08:00
2022-01-27 23:14:18 +08:00
if ( ruleset = = null )
{
log ( $"Skipping {++missing} beatmaps with missing ruleset" ) ;
continue ;
}
2022-01-24 17:55:15 +08:00
var realmBeatmap = new BeatmapInfo ( ruleset , new BeatmapDifficulty ( beatmap . BaseDifficulty ) , metadata )
2022-01-18 13:41:02 +08:00
{
2022-01-24 17:55:15 +08:00
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 ,
Bookmarks = beatmap . Bookmarks ,
BeatmapSet = realmBeatmapSet ,
} ;
realmBeatmapSet . Beatmaps . Add ( realmBeatmap ) ;
2022-01-13 17:02:08 +08:00
}
2022-01-19 15:01:17 +08:00
2022-01-25 12:04:05 +08:00
r . Add ( realmBeatmapSet ) ;
2022-01-24 17:55:15 +08:00
}
}
finally
{
transaction . Commit ( ) ;
2022-01-13 17:02:08 +08:00
}
2022-01-24 17:55:15 +08:00
log ( $"Successfully migrated {count} beatmaps to realm" ) ;
2022-01-21 16:08:20 +08:00
} ) ;
2022-01-13 17:02:08 +08:00
}
2022-01-14 22:31:42 +08:00
private BeatmapMetadata getBestMetadata ( EFBeatmapMetadata ? beatmapMetadata , EFBeatmapMetadata ? beatmapSetMetadata )
{
var metadata = beatmapMetadata ? ? beatmapSetMetadata ? ? new EFBeatmapMetadata ( ) ;
return new BeatmapMetadata
{
Title = metadata . Title ,
TitleUnicode = metadata . TitleUnicode ,
Artist = metadata . Artist ,
ArtistUnicode = metadata . ArtistUnicode ,
2022-01-19 15:22:17 +08:00
Author =
2022-01-14 22:31:42 +08:00
{
OnlineID = metadata . Author . Id ,
Username = metadata . Author . Username ,
} ,
Source = metadata . Source ,
Tags = metadata . Tags ,
PreviewTime = metadata . PreviewTime ,
AudioFile = metadata . AudioFile ,
BackgroundFile = metadata . BackgroundFile ,
} ;
}
2022-01-19 14:52:59 +08:00
private void migrateScores ( OsuDbContext db )
2022-01-13 16:51:23 +08:00
{
// can be removed 20220730.
2022-01-19 14:52:59 +08:00
var existingScores = db . ScoreInfo
2022-01-18 18:12:10 +08:00
. Include ( s = > s . Ruleset )
. Include ( s = > s . BeatmapInfo )
. Include ( s = > s . Files )
2022-02-15 14:23:14 +08:00
. ThenInclude ( f = > f . FileInfo )
. AsSplitQuery ( ) ;
2022-01-13 16:51:23 +08:00
2022-01-21 13:56:49 +08:00
log ( "Beginning scores migration to realm" ) ;
2022-01-18 13:41:02 +08:00
2022-01-13 16:51:23 +08:00
// previous entries in EF are removed post migration.
if ( ! existingScores . Any ( ) )
2022-01-18 13:41:02 +08:00
{
2022-01-21 13:56:49 +08:00
log ( "No scores found to migrate" ) ;
2022-01-13 16:51:23 +08:00
return ;
2022-01-18 13:41:02 +08:00
}
2022-01-18 13:19:31 +08:00
2022-01-18 18:12:10 +08:00
int count = existingScores . Count ( ) ;
2022-01-25 12:04:05 +08:00
realm . Run ( r = >
2022-01-13 16:51:23 +08:00
{
2022-01-21 13:56:49 +08:00
log ( $"Found {count} scores in EF" ) ;
2022-01-19 09:16:54 +08:00
2022-01-25 12:04:05 +08:00
var transaction = r . BeginWrite ( ) ;
2022-01-24 17:55:15 +08:00
int written = 0 ;
2022-01-26 17:04:53 +08:00
int missing = 0 ;
2022-01-19 15:01:17 +08:00
2022-01-24 17:55:15 +08:00
try
{
foreach ( var score in existingScores )
2022-01-13 16:51:23 +08:00
{
2022-01-24 17:55:15 +08:00
if ( + + written % 1000 = = 0 )
2022-01-13 16:51:23 +08:00
{
2022-01-24 17:55:15 +08:00
transaction . Commit ( ) ;
2022-01-25 12:04:05 +08:00
transaction = r . BeginWrite ( ) ;
2022-01-24 17:55:15 +08:00
log ( $"Migrated {written}/{count} scores..." ) ;
}
2022-01-18 13:41:02 +08:00
2022-01-28 00:20:32 +08:00
var beatmap = r . All < BeatmapInfo > ( ) . FirstOrDefault ( b = > b . Hash = = score . BeatmapInfo . Hash ) ;
2022-01-25 12:04:05 +08:00
var ruleset = r . Find < RulesetInfo > ( score . Ruleset . ShortName ) ;
2022-01-26 17:04:53 +08:00
2022-01-28 00:20:32 +08:00
if ( beatmap = = null | | ruleset = = null )
2022-01-26 17:04:53 +08:00
{
2022-01-28 00:20:32 +08:00
log ( $"Skipping {++missing} scores with missing ruleset or beatmap" ) ;
2022-01-26 17:04:53 +08:00
continue ;
}
2022-01-24 17:55:15 +08:00
var user = new RealmUser
{
OnlineID = score . User . OnlineID ,
Username = score . User . Username
} ;
2022-01-18 13:41:02 +08:00
2022-01-24 17:55:15 +08:00
var realmScore = new ScoreInfo ( beatmap , ruleset , user )
{
Hash = score . Hash ,
DeletePending = score . DeletePending ,
OnlineID = score . OnlineID ? ? - 1 ,
ModsJson = score . ModsJson ,
StatisticsJson = score . StatisticsJson ,
TotalScore = score . TotalScore ,
MaxCombo = score . MaxCombo ,
Accuracy = score . Accuracy ,
HasReplay = ( ( IScoreInfo ) score ) . HasReplay ,
Date = score . Date ,
PP = score . PP ,
Rank = score . Rank ,
HitEvents = score . HitEvents ,
Passed = score . Passed ,
Combo = score . Combo ,
Position = score . Position ,
Statistics = score . Statistics ,
Mods = score . Mods ,
APIMods = score . APIMods ,
} ;
2022-01-25 12:04:05 +08:00
migrateFiles ( score , r , realmScore ) ;
2022-01-24 17:55:15 +08:00
2022-01-25 12:04:05 +08:00
r . Add ( realmScore ) ;
2022-01-13 16:51:23 +08:00
}
}
2022-01-24 17:55:15 +08:00
finally
{
transaction . Commit ( ) ;
}
log ( $"Successfully migrated {count} scores to realm" ) ;
2022-01-21 16:08:20 +08:00
} ) ;
2021-11-30 14:41:18 +08:00
}
2022-01-19 14:52:59 +08:00
private void migrateSkins ( OsuDbContext db )
2021-11-30 14:41:18 +08:00
{
2021-12-07 03:12:02 +08:00
// can be removed 20220530.
2022-01-19 14:52:59 +08:00
var existingSkins = db . SkinInfo
2021-12-07 03:12:02 +08:00
. Include ( s = > s . Files )
. ThenInclude ( f = > f . FileInfo )
2022-02-15 14:23:14 +08:00
. AsSplitQuery ( )
2021-12-07 03:12:02 +08:00
. ToList ( ) ;
// previous entries in EF are removed post migration.
if ( ! existingSkins . Any ( ) )
return ;
2021-11-30 14:41:18 +08:00
var userSkinChoice = config . GetBindable < string > ( OsuSetting . Skin ) ;
int . TryParse ( userSkinChoice . Value , out int userSkinInt ) ;
switch ( userSkinInt )
{
case EFSkinInfo . DEFAULT_SKIN :
userSkinChoice . Value = SkinInfo . DEFAULT_SKIN . ToString ( ) ;
break ;
case EFSkinInfo . CLASSIC_SKIN :
userSkinChoice . Value = SkinInfo . CLASSIC_SKIN . ToString ( ) ;
break ;
}
2022-01-25 12:04:05 +08:00
realm . Run ( r = >
2021-11-30 14:41:18 +08:00
{
2022-01-25 12:04:05 +08:00
using ( var transaction = r . BeginWrite ( ) )
2021-11-30 14:41:18 +08:00
{
2022-01-21 16:08:20 +08:00
// only migrate data if the realm database is empty.
2022-01-25 12:04:05 +08:00
// note that this cannot be written as: `r.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
if ( ! r . All < SkinInfo > ( ) . Any ( s = > ! s . Protected ) )
2021-11-30 14:41:18 +08:00
{
2022-01-22 20:52:19 +08:00
log ( $"Migrating {existingSkins.Count} skins" ) ;
2022-01-21 16:08:20 +08:00
foreach ( var skin in existingSkins )
2021-11-30 14:41:18 +08:00
{
2022-01-21 16:08:20 +08:00
var realmSkin = new SkinInfo
{
Name = skin . Name ,
Creator = skin . Creator ,
Hash = skin . Hash ,
Protected = false ,
InstantiationInfo = skin . InstantiationInfo ,
} ;
2021-11-30 14:41:18 +08:00
2022-01-25 12:04:05 +08:00
migrateFiles ( skin , r , realmSkin ) ;
2021-11-30 14:41:18 +08:00
2022-01-25 12:04:05 +08:00
r . Add ( realmSkin ) ;
2021-11-30 14:41:18 +08:00
2022-01-21 16:08:20 +08:00
if ( skin . ID = = userSkinInt )
userSkinChoice . Value = realmSkin . ID . ToString ( ) ;
}
2021-11-30 14:41:18 +08:00
}
2022-01-21 16:08:20 +08:00
transaction . Commit ( ) ;
}
} ) ;
2021-11-30 14:41:18 +08:00
}
2022-01-13 16:51:23 +08:00
private static void migrateFiles < T > ( IHasFiles < T > fileSource , Realm realm , IHasRealmFiles realmObject ) where T : INamedFileInfo
{
foreach ( var file in fileSource . Files )
{
var realmFile = realm . Find < RealmFile > ( file . FileInfo . Hash ) ;
if ( realmFile = = null )
realm . Add ( realmFile = new RealmFile { Hash = file . FileInfo . Hash } ) ;
realmObject . Files . Add ( new RealmNamedFileUsage ( realmFile , file . Filename ) ) ;
}
}
2022-01-19 14:52:59 +08:00
private void migrateSettings ( OsuDbContext db )
2021-11-30 14:41:18 +08:00
{
// migrate ruleset settings. can be removed 20220315.
2022-01-19 14:52:59 +08:00
var existingSettings = db . DatabasedSetting . ToList ( ) ;
2021-11-30 14:41:18 +08:00
// previous entries in EF are removed post migration.
if ( ! existingSettings . Any ( ) )
return ;
2022-01-21 13:56:49 +08:00
log ( "Beginning settings migration to realm" ) ;
2022-01-18 13:19:31 +08:00
2022-01-25 12:04:05 +08:00
realm . Run ( r = >
2021-11-30 14:41:18 +08:00
{
2022-01-25 12:04:05 +08:00
using ( var transaction = r . BeginWrite ( ) )
2021-11-30 14:41:18 +08:00
{
2022-01-21 16:08:20 +08:00
// only migrate data if the realm database is empty.
2022-01-25 12:04:05 +08:00
if ( ! r . All < RealmRulesetSetting > ( ) . Any ( ) )
2021-11-30 14:41:18 +08:00
{
2022-01-22 20:52:19 +08:00
log ( $"Migrating {existingSettings.Count} settings" ) ;
2021-11-30 14:41:18 +08:00
2022-01-21 16:08:20 +08:00
foreach ( var dkb in existingSettings )
{
if ( dkb . RulesetID = = null )
continue ;
2021-11-30 14:41:18 +08:00
2022-01-21 16:08:20 +08:00
string? shortName = getRulesetShortNameFromLegacyID ( dkb . RulesetID . Value ) ;
2021-11-30 14:41:18 +08:00
2022-01-21 16:08:20 +08:00
if ( string . IsNullOrEmpty ( shortName ) )
continue ;
2022-01-25 12:04:05 +08:00
r . Add ( new RealmRulesetSetting
2022-01-21 16:08:20 +08:00
{
Key = dkb . Key ,
Value = dkb . StringValue ,
RulesetName = shortName ,
Variant = dkb . Variant ? ? 0 ,
} ) ;
}
2021-11-30 14:41:18 +08:00
}
2022-01-21 16:08:20 +08:00
transaction . Commit ( ) ;
}
} ) ;
2021-11-30 14:41:18 +08:00
}
private string? getRulesetShortNameFromLegacyID ( long rulesetId ) = >
efContextFactory . Get ( ) . RulesetInfo . FirstOrDefault ( r = > r . ID = = rulesetId ) ? . ShortName ;
}
}