diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 27bc803449..96817571f6 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -80,6 +80,8 @@ namespace osu.Game.Beatmaps public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) { + Debug.Assert(beatmapInfo.BeatmapSet != null); + if (!Available) { onlineMetadata = null; @@ -94,43 +96,21 @@ namespace osu.Game.Beatmaps return false; } - Debug.Assert(beatmapInfo.BeatmapSet != null); - try { using (var db = new SqliteConnection(string.Concat(@"Data Source=", storage.GetFullPath(@"online.db", true)))) { db.Open(); - using (var cmd = db.CreateCommand()) + switch (getCacheVersion(db)) { - cmd.CommandText = - @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + case 1: + // will eventually become irrelevant due to the monthly recycling of local caches + // can be removed 20250221 + return queryCacheVersion1(db, beatmapInfo, out onlineMetadata); - cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); - cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo}."); - - onlineMetadata = new OnlineBeatmapMetadata - { - BeatmapSetID = reader.GetInt32(0), - BeatmapID = reader.GetInt32(1), - BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), - BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), - AuthorID = reader.GetInt32(3), - MD5Hash = reader.GetString(4), - LastUpdated = reader.GetDateTimeOffset(5), - // TODO: DateSubmitted and DateRanked are not provided by local cache. - }; - return true; - } - } + case 2: + return queryCacheVersion2(db, beatmapInfo, out onlineMetadata); } } } @@ -211,6 +191,115 @@ namespace osu.Game.Beatmaps }); } + private int getCacheVersion(SqliteConnection connection) + { + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = @"SELECT COUNT(1) FROM `sqlite_master` WHERE `type` = 'table' AND `name` = 'schema_version'"; + + using var reader = cmd.ExecuteReader(); + + if (!reader.Read()) + throw new InvalidOperationException("Error when attempting to check for existence of `schema_version` table."); + + // No versioning table means that this is the very first version of the schema. + if (reader.GetInt32(0) == 0) + return 1; + } + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = @"SELECT `number` FROM `schema_version`"; + + using var reader = cmd.ExecuteReader(); + + if (!reader.Read()) + throw new InvalidOperationException("Error when attempting to query schema version."); + + return reader.GetInt32(0); + } + } + + private bool queryCacheVersion1(SqliteConnection db, BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) + { + Debug.Assert(beatmapInfo.BeatmapSet != null); + + using var cmd = db.CreateCommand(); + + cmd.CommandText = + @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + + cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); + + using var reader = cmd.ExecuteReader(); + + if (reader.Read()) + { + logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} (cache version 1)."); + + onlineMetadata = new OnlineBeatmapMetadata + { + BeatmapSetID = reader.GetInt32(0), + BeatmapID = reader.GetInt32(1), + BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), + BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), + AuthorID = reader.GetInt32(3), + MD5Hash = reader.GetString(4), + LastUpdated = reader.GetDateTimeOffset(5), + // TODO: DateSubmitted and DateRanked are not provided by local cache in this version. + }; + return true; + } + + onlineMetadata = null; + return false; + } + + private bool queryCacheVersion2(SqliteConnection db, BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) + { + Debug.Assert(beatmapInfo.BeatmapSet != null); + + using var cmd = db.CreateCommand(); + + cmd.CommandText = + """ + SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date` + FROM `osu_beatmaps` AS `b` + JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id` + WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path + """; + + cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); + + using var reader = cmd.ExecuteReader(); + + if (reader.Read()) + { + logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} (cache version 2)."); + + onlineMetadata = new OnlineBeatmapMetadata + { + BeatmapSetID = reader.GetInt32(0), + BeatmapID = reader.GetInt32(1), + BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), + BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), + AuthorID = reader.GetInt32(3), + MD5Hash = reader.GetString(4), + LastUpdated = reader.GetDateTimeOffset(5), + DateSubmitted = reader.GetDateTimeOffset(6), + DateRanked = reader.GetDateTimeOffset(7), + }; + return true; + } + + onlineMetadata = null; + return false; + } + private static void log(string message) => Logger.Log($@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}", LoggingTarget.Database);