2017-07-27 15:56:41 +08:00
|
|
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
2017-07-26 15:28:32 +08:00
|
|
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
|
|
|
|
|
|
using System;
|
2017-10-05 03:52:12 +08:00
|
|
|
using System.Linq;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2017-07-27 15:56:41 +08:00
|
|
|
using osu.Game.Database;
|
2017-07-26 15:28:32 +08:00
|
|
|
|
|
|
|
namespace osu.Game.Beatmaps
|
|
|
|
{
|
|
|
|
/// <summary>
|
2017-07-27 15:56:41 +08:00
|
|
|
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
|
2017-07-26 15:28:32 +08:00
|
|
|
/// </summary>
|
2017-07-27 15:56:41 +08:00
|
|
|
public class BeatmapStore : DatabaseBackedStore
|
2017-07-26 15:28:32 +08:00
|
|
|
{
|
|
|
|
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
2017-07-26 22:13:02 +08:00
|
|
|
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
2017-07-26 15:28:32 +08:00
|
|
|
|
2017-08-31 14:49:56 +08:00
|
|
|
public event Action<BeatmapInfo> BeatmapHidden;
|
|
|
|
public event Action<BeatmapInfo> BeatmapRestored;
|
|
|
|
|
2017-10-17 14:00:27 +08:00
|
|
|
public BeatmapStore(Func<OsuDbContext> factory)
|
|
|
|
: base(factory)
|
2017-07-26 15:28:32 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2017-07-27 15:56:41 +08:00
|
|
|
/// Add a <see cref="BeatmapSetInfo"/> to the database.
|
2017-07-26 15:28:32 +08:00
|
|
|
/// </summary>
|
2017-07-27 15:56:41 +08:00
|
|
|
/// <param name="beatmapSet">The beatmap to add.</param>
|
|
|
|
public void Add(BeatmapSetInfo beatmapSet)
|
2017-07-26 15:28:32 +08:00
|
|
|
{
|
2017-10-17 14:50:42 +08:00
|
|
|
var context = GetContext();
|
|
|
|
|
2017-11-28 03:08:16 +08:00
|
|
|
foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
|
|
|
|
{
|
2017-12-07 21:14:50 +08:00
|
|
|
// If we detect a new metadata object it'll be attached to the current context so it can be reused
|
|
|
|
// to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
|
|
|
|
// of the corresponding table (.Set<BeatmapMetadata>()) for matching entries to our criteria.
|
2017-11-28 03:08:16 +08:00
|
|
|
var contextMetadata = context.Set<BeatmapMetadata>().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
|
|
|
|
if (contextMetadata != null)
|
2017-12-07 20:56:37 +08:00
|
|
|
beatmap.Metadata = contextMetadata;
|
2017-11-28 03:08:16 +08:00
|
|
|
else
|
2017-12-07 21:14:50 +08:00
|
|
|
context.BeatmapMetadata.Attach(beatmap.Metadata);
|
2017-11-28 03:08:16 +08:00
|
|
|
}
|
|
|
|
|
2017-10-17 14:50:42 +08:00
|
|
|
context.BeatmapSetInfo.Attach(beatmapSet);
|
|
|
|
context.SaveChanges();
|
2017-07-28 16:31:13 +08:00
|
|
|
|
2017-07-27 15:56:41 +08:00
|
|
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
2017-07-26 15:28:32 +08:00
|
|
|
}
|
|
|
|
|
2017-07-26 22:13:02 +08:00
|
|
|
/// <summary>
|
2017-08-31 14:49:56 +08:00
|
|
|
/// Delete a <see cref="BeatmapSetInfo"/> from the database.
|
2017-07-26 22:13:02 +08:00
|
|
|
/// </summary>
|
2017-07-27 15:56:41 +08: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 22:13:02 +08:00
|
|
|
{
|
2017-10-17 14:50:42 +08:00
|
|
|
var context = GetContext();
|
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
Refresh(ref beatmapSet, BeatmapSets);
|
2017-07-27 14:34:13 +08:00
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
if (beatmapSet.DeletePending) return false;
|
2017-07-27 15:56:41 +08:00
|
|
|
beatmapSet.DeletePending = true;
|
2017-10-17 14:50:42 +08:00
|
|
|
context.SaveChanges();
|
2017-07-27 14:34:13 +08:00
|
|
|
|
2017-07-27 15:56:41 +08:00
|
|
|
BeatmapSetRemoved?.Invoke(beatmapSet);
|
|
|
|
return true;
|
2017-07-27 14:34:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2017-07-27 15:56:41 +08:00
|
|
|
/// Restore a previously deleted <see cref="BeatmapSetInfo"/>.
|
2017-07-27 14:34:13 +08:00
|
|
|
/// </summary>
|
2017-07-27 15:56:41 +08: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 14:34:13 +08:00
|
|
|
{
|
2017-10-17 14:50:42 +08:00
|
|
|
var context = GetContext();
|
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
Refresh(ref beatmapSet, BeatmapSets);
|
2017-07-26 22:13:02 +08:00
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
if (!beatmapSet.DeletePending) return false;
|
2017-07-27 15:56:41 +08:00
|
|
|
beatmapSet.DeletePending = false;
|
2017-10-17 14:50:42 +08:00
|
|
|
context.SaveChanges();
|
2017-07-27 14:34:13 +08:00
|
|
|
|
2017-07-27 15:56:41 +08:00
|
|
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
|
|
|
return true;
|
2017-07-26 19:22:02 +08:00
|
|
|
}
|
|
|
|
|
2017-08-31 14:49:56 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Hide a <see cref="BeatmapInfo"/> in the database.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="beatmap">The beatmap to hide.</param>
|
|
|
|
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
|
|
|
public bool Hide(BeatmapInfo beatmap)
|
|
|
|
{
|
2017-10-17 14:50:42 +08:00
|
|
|
var context = GetContext();
|
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
Refresh(ref beatmap, Beatmaps);
|
2017-08-31 14:49:56 +08:00
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
if (beatmap.Hidden) return false;
|
2017-08-31 14:49:56 +08:00
|
|
|
beatmap.Hidden = true;
|
2017-10-17 14:50:42 +08:00
|
|
|
context.SaveChanges();
|
2017-08-31 14:49:56 +08:00
|
|
|
|
|
|
|
BeatmapHidden?.Invoke(beatmap);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Restore a previously hidden <see cref="BeatmapInfo"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="beatmap">The beatmap to restore.</param>
|
|
|
|
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
|
|
|
public bool Restore(BeatmapInfo beatmap)
|
|
|
|
{
|
2017-10-17 14:50:42 +08:00
|
|
|
var context = GetContext();
|
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
Refresh(ref beatmap, Beatmaps);
|
2017-08-31 14:49:56 +08:00
|
|
|
|
2017-10-25 21:07:32 +08:00
|
|
|
if (!beatmap.Hidden) return false;
|
2017-08-31 14:49:56 +08:00
|
|
|
beatmap.Hidden = false;
|
2017-10-17 14:50:42 +08:00
|
|
|
context.SaveChanges();
|
2017-08-31 14:49:56 +08:00
|
|
|
|
|
|
|
BeatmapRestored?.Invoke(beatmap);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-17 18:58:33 +08:00
|
|
|
public override void Cleanup()
|
2017-07-26 15:28:32 +08:00
|
|
|
{
|
2017-10-17 14:50:42 +08:00
|
|
|
var context = GetContext();
|
|
|
|
|
2017-10-19 16:50:46 +08:00
|
|
|
var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
|
|
|
|
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
|
|
|
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
|
|
|
.Include(s => s.Metadata);
|
|
|
|
|
|
|
|
// metadata is M-N so we can't rely on cascades
|
|
|
|
context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
|
2017-10-20 10:14:45 +08:00
|
|
|
context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
|
2017-10-19 16:50:46 +08:00
|
|
|
|
|
|
|
// todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly.
|
|
|
|
context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
|
|
|
|
|
|
|
|
// cascades down to beatmaps.
|
|
|
|
context.BeatmapSetInfo.RemoveRange(purgeable);
|
2017-10-17 14:50:42 +08:00
|
|
|
context.SaveChanges();
|
2017-10-05 03:52:12 +08:00
|
|
|
}
|
2017-10-16 11:56:58 +08:00
|
|
|
|
2017-10-25 16:03:10 +08:00
|
|
|
public IQueryable<BeatmapSetInfo> BeatmapSets => GetContext().BeatmapSetInfo
|
2017-10-25 22:56:18 +08:00
|
|
|
.Include(s => s.Metadata)
|
|
|
|
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
|
|
|
|
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
|
|
|
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
|
|
|
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
|
2017-10-17 14:50:42 +08:00
|
|
|
|
2017-10-25 16:03:10 +08:00
|
|
|
public IQueryable<BeatmapInfo> Beatmaps => GetContext().BeatmapInfo
|
2017-10-25 22:56:18 +08:00
|
|
|
.Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
|
|
|
|
.Include(b => b.Metadata)
|
|
|
|
.Include(b => b.Ruleset)
|
|
|
|
.Include(b => b.BaseDifficulty);
|
2017-07-26 15:28:32 +08:00
|
|
|
}
|
|
|
|
}
|