// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using osu.Framework.Logging; using osu.Game.Database; using SQLite.Net; using SQLiteNetExtensions.Extensions; namespace osu.Game.Beatmaps { /// /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing /// public class BeatmapStore : DatabaseBackedStore { public event Action BeatmapSetAdded; public event Action BeatmapSetRemoved; /// /// The current version of this store. Used for migrations (see ). /// The initial version is 1. /// protected override int StoreVersion => 1; public BeatmapStore(SQLiteConnection connection) : base(connection) { } protected override Type[] ValidTypes => new[] { typeof(BeatmapSetInfo), typeof(BeatmapInfo), typeof(BeatmapMetadata), typeof(BeatmapDifficulty), }; protected override void Prepare(bool reset = false) { if (reset) { Connection.DropTable(); Connection.DropTable(); Connection.DropTable(); Connection.DropTable(); Connection.DropTable(); } Connection.CreateTable(); Connection.CreateTable(); Connection.CreateTable(); Connection.CreateTable(); Connection.CreateTable(); cleanupPendingDeletions(); } /// /// Perform migrations between two store versions. /// /// The current store version. This will be zero on a fresh database initialisation. /// The target version which we are migrating to (equal to the current ). protected override void PerformMigration(int currentVersion, int newVersion) { base.PerformMigration(currentVersion, newVersion); while (currentVersion++ < newVersion) { switch (currentVersion) { case 1: // initialising from a version before we had versioning (or a fresh install). // force adding of Protected column (not automatically migrated). Connection.MigrateTable(); // remove all existing beatmaps. foreach (var b in Connection.GetAllWithChildren(null, true)) Connection.Delete(b, true); break; } } } /// /// Add a to the database. /// /// The beatmap to add. public void Add(BeatmapSetInfo beatmapSet) { Connection.InsertOrReplaceWithChildren(beatmapSet, true); BeatmapSetAdded?.Invoke(beatmapSet); } /// /// Delete a to the database. /// /// The beatmap to delete. /// Whether the beatmap's was changed. public bool Delete(BeatmapSetInfo beatmapSet) { if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; Connection.Update(beatmapSet); BeatmapSetRemoved?.Invoke(beatmapSet); return true; } /// /// Restore a previously deleted . /// /// The beatmap to restore. /// Whether the beatmap's was changed. public bool Undelete(BeatmapSetInfo beatmapSet) { if (!beatmapSet.DeletePending) return false; beatmapSet.DeletePending = false; Connection.Update(beatmapSet); BeatmapSetAdded?.Invoke(beatmapSet); return true; } private void cleanupPendingDeletions() { foreach (var b in QueryAndPopulate(b => b.DeletePending && !b.Protected)) { try { // many-to-many join table entries are not automatically tidied. Connection.Table().Delete(f => f.BeatmapSetInfoID == b.ID); Connection.Delete(b, true); } catch (Exception e) { Logger.Error(e, $@"Could not delete beatmap {b}"); } } //this is required because sqlite migrations don't work, initially inserting nulls into this field. //see https://github.com/praeclarum/sqlite-net/issues/326 Connection.Query("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL"); } } }