2017-07-27 07:56:41 +00:00
|
|
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
2017-07-26 07:28:32 +00:00
|
|
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using osu.Framework.Logging;
|
2017-07-27 07:56:41 +00:00
|
|
|
using osu.Game.Database;
|
2017-07-26 07:28:32 +00:00
|
|
|
using SQLite.Net;
|
2017-07-27 07:56:41 +00:00
|
|
|
using SQLiteNetExtensions.Extensions;
|
2017-07-26 07:28:32 +00:00
|
|
|
|
|
|
|
namespace osu.Game.Beatmaps
|
|
|
|
{
|
|
|
|
/// <summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
|
2017-07-26 07:28:32 +00:00
|
|
|
/// </summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
public class BeatmapStore : DatabaseBackedStore
|
2017-07-26 07:28:32 +00:00
|
|
|
{
|
|
|
|
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
2017-07-26 14:13:02 +00:00
|
|
|
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
2017-07-26 07:28:32 +00:00
|
|
|
|
2017-07-27 11:38:35 +00:00
|
|
|
/// <summary>
|
|
|
|
/// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>).
|
|
|
|
/// The initial version is 1.
|
|
|
|
/// </summary>
|
|
|
|
protected override int StoreVersion => 1;
|
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
public BeatmapStore(SQLiteConnection connection)
|
|
|
|
: base(connection)
|
2017-07-26 07:28:32 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
protected override Type[] ValidTypes => new[]
|
2017-07-26 07:28:32 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
typeof(BeatmapSetInfo),
|
|
|
|
typeof(BeatmapInfo),
|
|
|
|
typeof(BeatmapMetadata),
|
|
|
|
typeof(BeatmapDifficulty),
|
|
|
|
};
|
2017-07-26 07:28:32 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
protected override void Prepare(bool reset = false)
|
2017-07-26 07:28:32 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
if (reset)
|
|
|
|
{
|
|
|
|
Connection.DropTable<BeatmapMetadata>();
|
|
|
|
Connection.DropTable<BeatmapDifficulty>();
|
|
|
|
Connection.DropTable<BeatmapSetInfo>();
|
|
|
|
Connection.DropTable<BeatmapSetFileInfo>();
|
|
|
|
Connection.DropTable<BeatmapInfo>();
|
|
|
|
}
|
2017-07-26 07:28:32 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
Connection.CreateTable<BeatmapMetadata>();
|
|
|
|
Connection.CreateTable<BeatmapDifficulty>();
|
|
|
|
Connection.CreateTable<BeatmapSetInfo>();
|
|
|
|
Connection.CreateTable<BeatmapSetFileInfo>();
|
|
|
|
Connection.CreateTable<BeatmapInfo>();
|
2017-07-26 07:28:32 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
cleanupPendingDeletions();
|
2017-07-26 07:28:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-27 11:38:35 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Perform migrations between two store versions.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="currentVersion">The current store version. This will be zero on a fresh database initialisation.</param>
|
|
|
|
/// <param name="newVersion">The target version which we are migrating to (equal to the current <see cref="StoreVersion"/>).</param>
|
|
|
|
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<BeatmapSetInfo>();
|
|
|
|
|
|
|
|
// remove all existing beatmaps.
|
|
|
|
foreach (var b in Connection.GetAllWithChildren<BeatmapSetInfo>(null, true))
|
|
|
|
Connection.Delete(b, true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-26 07:28:32 +00:00
|
|
|
/// <summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// Add a <see cref="BeatmapSetInfo"/> to the database.
|
2017-07-26 07:28:32 +00:00
|
|
|
/// </summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// <param name="beatmapSet">The beatmap to add.</param>
|
|
|
|
public void Add(BeatmapSetInfo beatmapSet)
|
2017-07-26 07:28:32 +00:00
|
|
|
{
|
2017-07-28 08:31:13 +00:00
|
|
|
Connection.RunInTransaction(() =>
|
|
|
|
{
|
|
|
|
Connection.InsertOrReplaceWithChildren(beatmapSet, true);
|
|
|
|
});
|
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
2017-07-26 07:28:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 14:13:02 +00:00
|
|
|
/// <summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// Delete a <see cref="BeatmapSetInfo"/> to the database.
|
2017-07-26 14:13:02 +00:00
|
|
|
/// </summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// <param name="beatmapSet">The beatmap to delete.</param>
|
|
|
|
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
|
|
|
public bool Delete(BeatmapSetInfo beatmapSet)
|
2017-07-26 14:13:02 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
if (beatmapSet.DeletePending) return false;
|
2017-07-27 06:34:13 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
beatmapSet.DeletePending = true;
|
|
|
|
Connection.Update(beatmapSet);
|
2017-07-27 06:34:13 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
BeatmapSetRemoved?.Invoke(beatmapSet);
|
|
|
|
return true;
|
2017-07-27 06:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// Restore a previously deleted <see cref="BeatmapSetInfo"/>.
|
2017-07-27 06:34:13 +00:00
|
|
|
/// </summary>
|
2017-07-27 07:56:41 +00:00
|
|
|
/// <param name="beatmapSet">The beatmap to restore.</param>
|
|
|
|
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
|
|
|
public bool Undelete(BeatmapSetInfo beatmapSet)
|
2017-07-27 06:34:13 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
if (!beatmapSet.DeletePending) return false;
|
2017-07-26 14:13:02 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
beatmapSet.DeletePending = false;
|
|
|
|
Connection.Update(beatmapSet);
|
2017-07-27 06:34:13 +00:00
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
|
|
|
return true;
|
2017-07-26 11:22:02 +00:00
|
|
|
}
|
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
private void cleanupPendingDeletions()
|
2017-07-26 07:28:32 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
foreach (var b in QueryAndPopulate<BeatmapSetInfo>(b => b.DeletePending && !b.Protected))
|
2017-07-27 06:50:34 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
try
|
2017-07-26 11:22:02 +00:00
|
|
|
{
|
2017-07-27 07:56:41 +00:00
|
|
|
// many-to-many join table entries are not automatically tidied.
|
|
|
|
Connection.Table<BeatmapSetFileInfo>().Delete(f => f.BeatmapSetInfoID == b.ID);
|
|
|
|
Connection.Delete(b, true);
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
Logger.Error(e, $@"Could not delete beatmap {b}");
|
2017-07-26 11:22:02 +00:00
|
|
|
}
|
2017-07-26 07:28:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-27 07:56:41 +00:00
|
|
|
//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<BeatmapSetInfo>("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL");
|
2017-07-27 06:34:13 +00:00
|
|
|
}
|
2017-07-26 07:28:32 +00:00
|
|
|
}
|
|
|
|
}
|