1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:57:39 +08:00

Make deletion and purging logic even more global

This commit is contained in:
Dean Herbert 2018-02-15 13:30:17 +09:00
parent d340509b1d
commit d3dd31dadb
9 changed files with 114 additions and 91 deletions

View File

@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
{
if (deleteMaps)
{
manager.DeleteAll();
manager.Delete(manager.GetAllUsableBeatmapSets());
game.Beatmap.SetDefault();
}

View File

@ -71,8 +71,6 @@ namespace osu.Game.Beatmaps
this.rulesets = rulesets;
this.api = api;
beatmaps.Cleanup();
}
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
@ -102,7 +100,7 @@ namespace osu.Game.Beatmaps
if (existingOnlineId != null)
{
Delete(existingOnlineId);
beatmaps.Cleanup(s => s.ID == existingOnlineId.ID);
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
}
}
@ -193,12 +191,6 @@ namespace osu.Game.Beatmaps
/// <returns>The <see cref="DownloadBeatmapSetRequest"/> object if it exists, or null.</returns>
public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID);
/// <summary>
/// Update a BeatmapSetInfo with all changes. TODO: This only supports very basic updates currently.
/// </summary>
/// <param name="beatmapSet">The beatmap set to update.</param>
public void Update(BeatmapSetInfo beatmap) => beatmaps.Update(beatmap);
/// <summary>
/// Delete a beatmap difficulty.
/// </summary>
@ -239,13 +231,6 @@ namespace osu.Game.Beatmaps
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapSetInfo QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.BeatmapSets.AsNoTracking().FirstOrDefault(query);
/// <summary>
/// Refresh an existing instance of a <see cref="BeatmapSetInfo"/> from the store.
/// </summary>
/// <param name="beatmapSet">A stale instance.</param>
/// <returns>A fresh instance.</returns>
public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary>
@ -294,41 +279,6 @@ namespace osu.Game.Beatmaps
await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning);
}
/// <summary>
/// Delete all beatmaps.
/// This will post notifications tracking progress.
/// </summary>
public void DeleteAll()
{
var maps = GetAllUsableBeatmapSets();
if (maps.Count == 0) return;
var notification = new ProgressNotification
{
Progress = 0,
CompletionText = "Deleted all beatmaps!",
State = ProgressNotificationState.Active,
};
PostNotification?.Invoke(notification);
int i = 0;
foreach (var b in maps)
{
if (notification.State == ProgressNotificationState.Cancelled)
// user requested abort
return;
notification.Text = $"Deleting ({i} of {maps.Count})";
notification.Progress = (float)++i / maps.Count;
Delete(b);
}
notification.State = ProgressNotificationState.Completed;
}
/// <summary>
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
/// </summary>

View File

@ -2,8 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using osu.Game.Database;
@ -63,32 +63,24 @@ namespace osu.Game.Beatmaps
return true;
}
public override void Cleanup() => Cleanup(_ => true);
public void Cleanup(Expression<Func<BeatmapSetInfo, bool>> query)
protected override IQueryable<BeatmapSetInfo> AddIncludesForDeletion(IQueryable<BeatmapSetInfo> query)
{
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
return base.AddIncludesForDeletion(query)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Metadata);
}
var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
.Where(query)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Metadata).ToList();
protected override void Purge(List<BeatmapSetInfo> items, OsuDbContext context)
{
// metadata is M-N so we can't rely on cascades
context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata));
context.BeatmapMetadata.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
if (!purgeable.Any()) return;
// todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly.
context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
// metadata is M-N so we can't rely on cascades
context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
// 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);
}
base.Purge(items, context);
}
public IQueryable<BeatmapSetInfo> BeatmapSets => ContextFactory.Get().BeatmapSetInfo

View File

@ -62,6 +62,8 @@ namespace osu.Game.Database
if (importHost != null)
ipc = new ArchiveImportIPCChannel(importHost, this);
ModelStore.PurgeDeletable();
}
/// <summary>
@ -154,6 +156,13 @@ namespace osu.Game.Database
/// <param name="item">The model to be imported.</param>
public void Import(TModel item) => ModelStore.Add(item);
/// <summary>
/// Perform an update of the specified item.
/// TODO: Support file changes.
/// </summary>
/// <param name="item">The item to update.</param>
public void Update(TModel item) => ModelStore.Update(item);
/// <summary>
/// Delete an item from the manager.
/// Is a no-op for already deleted items.
@ -180,14 +189,48 @@ namespace osu.Game.Database
}
/// <summary>
/// Restore all items that were previously deleted.
/// Delete multiple items.
/// This will post notifications tracking progress.
/// </summary>
public void UndeleteAll()
public void Delete(List<TModel> items)
{
var deletedItems = queryModel().Where(m => m.DeletePending).ToList();
if (items.Count == 0) return;
if (!deletedItems.Any()) return;
var notification = new ProgressNotification
{
Progress = 0,
CompletionText = "Deleted all beatmaps!",
State = ProgressNotificationState.Active,
};
PostNotification?.Invoke(notification);
int i = 0;
using (ContextFactory.GetForWrite())
{
foreach (var b in items)
{
if (notification.State == ProgressNotificationState.Cancelled)
// user requested abort
return;
notification.Text = $"Deleting ({i} of {items.Count})";
notification.Progress = (float)++i / items.Count;
Delete(b);
}
}
notification.State = ProgressNotificationState.Completed;
}
/// <summary>
/// Restore multiple items that were previously deleted.
/// This will post notifications tracking progress.
/// </summary>
public void Undelete(List<TModel> items)
{
if (!items.Any()) return;
var notification = new ProgressNotification
{
@ -200,15 +243,18 @@ namespace osu.Game.Database
int i = 0;
foreach (var item in deletedItems)
using (ContextFactory.GetForWrite())
{
if (notification.State == ProgressNotificationState.Cancelled)
// user requested abort
return;
foreach (var item in items)
{
if (notification.State == ProgressNotificationState.Cancelled)
// user requested abort
return;
notification.Text = $"Restoring ({i} of {deletedItems.Count})";
notification.Progress = (float)++i / deletedItems.Count;
Undelete(item);
notification.Text = $"Restoring ({i} of {items.Count})";
notification.Progress = (float)++i / items.Count;
Undelete(item);
}
}
notification.State = ProgressNotificationState.Completed;

View File

@ -49,7 +49,7 @@ namespace osu.Game.Database
/// <summary>
/// Perform any common clean-up tasks. Should be run when idle, or whenever necessary.
/// </summary>
public virtual void Cleanup()
public virtual void PurgeDeletable()
{
}
}

View File

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using osu.Framework.Platform;
namespace osu.Game.Database
@ -72,5 +75,37 @@ namespace osu.Game.Database
ItemAdded?.Invoke(item);
return true;
}
protected virtual IQueryable<T> AddIncludesForDeletion(IQueryable<T> query) => query;
protected virtual void Purge(List<T> items, OsuDbContext context)
{
// cascades down to beatmaps.
context.RemoveRange(items);
}
/// <summary>
/// Purge items in a pending delete state.
/// </summary>
/// <param name="query">An optional query limiting the scope of the purge.</param>
public void PurgeDeletable(Expression<Func<T, bool>> query = null)
{
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
var lookup = context.Set<T>().Where(s => s.DeletePending);
if (query != null) lookup = lookup.Where(query);
AddIncludesForDeletion(lookup);
var purgeable = lookup.ToList();
if (!purgeable.Any()) return;
Purge(purgeable, context);
}
}
}
}

View File

@ -90,7 +90,7 @@ namespace osu.Game.IO
}
}
public override void Cleanup()
public override void PurgeDeletable()
{
using (var usage = ContextFactory.GetForWrite())
{

View File

@ -172,7 +172,7 @@ namespace osu.Game
API.Register(this);
FileStore.Cleanup();
FileStore.PurgeDeletable();
}
private void runMigrations()

View File

@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
{
deleteButton.Enabled.Value = false;
Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
}));
}
},
@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Action = () =>
{
undeleteButton.Enabled.Value = false;
Task.Run(() => beatmaps.UndeleteAll()).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true));
Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.DeletePending))).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true));
}
},
};