mirror of
https://github.com/osukey/osukey.git
synced 2025-07-02 08:49:59 +09:00
Make deletion and purging logic even more global
This commit is contained in:
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
if (deleteMaps)
|
if (deleteMaps)
|
||||||
{
|
{
|
||||||
manager.DeleteAll();
|
manager.Delete(manager.GetAllUsableBeatmapSets());
|
||||||
game.Beatmap.SetDefault();
|
game.Beatmap.SetDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +71,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
beatmaps.Cleanup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
||||||
@ -102,7 +100,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (existingOnlineId != null)
|
if (existingOnlineId != null)
|
||||||
{
|
{
|
||||||
Delete(existingOnlineId);
|
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>
|
/// <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);
|
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>
|
/// <summary>
|
||||||
/// Delete a beatmap difficulty.
|
/// Delete a beatmap difficulty.
|
||||||
/// </summary>
|
/// </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>
|
/// <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);
|
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>
|
/// <summary>
|
||||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -294,41 +279,6 @@ namespace osu.Game.Beatmaps
|
|||||||
await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning);
|
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>
|
/// <summary>
|
||||||
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
|
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
|
||||||
@ -63,32 +63,24 @@ namespace osu.Game.Beatmaps
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Cleanup() => Cleanup(_ => true);
|
protected override IQueryable<BeatmapSetInfo> AddIncludesForDeletion(IQueryable<BeatmapSetInfo> query)
|
||||||
|
|
||||||
public void Cleanup(Expression<Func<BeatmapSetInfo, bool>> query)
|
|
||||||
{
|
{
|
||||||
using (var usage = ContextFactory.GetForWrite())
|
return base.AddIncludesForDeletion(query)
|
||||||
{
|
|
||||||
var context = usage.Context;
|
|
||||||
|
|
||||||
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.Metadata)
|
||||||
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
||||||
.Include(s => s.Metadata).ToList();
|
.Include(s => s.Metadata);
|
||||||
|
}
|
||||||
if (!purgeable.Any()) return;
|
|
||||||
|
|
||||||
|
protected override void Purge(List<BeatmapSetInfo> items, OsuDbContext context)
|
||||||
|
{
|
||||||
// metadata is M-N so we can't rely on cascades
|
// metadata is M-N so we can't rely on cascades
|
||||||
context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
|
context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata));
|
||||||
context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
|
context.BeatmapMetadata.RemoveRange(items.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.
|
// 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)));
|
context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
|
||||||
|
|
||||||
// cascades down to beatmaps.
|
base.Purge(items, context);
|
||||||
context.BeatmapSetInfo.RemoveRange(purgeable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IQueryable<BeatmapSetInfo> BeatmapSets => ContextFactory.Get().BeatmapSetInfo
|
public IQueryable<BeatmapSetInfo> BeatmapSets => ContextFactory.Get().BeatmapSetInfo
|
||||||
|
@ -62,6 +62,8 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
if (importHost != null)
|
if (importHost != null)
|
||||||
ipc = new ArchiveImportIPCChannel(importHost, this);
|
ipc = new ArchiveImportIPCChannel(importHost, this);
|
||||||
|
|
||||||
|
ModelStore.PurgeDeletable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -154,6 +156,13 @@ namespace osu.Game.Database
|
|||||||
/// <param name="item">The model to be imported.</param>
|
/// <param name="item">The model to be imported.</param>
|
||||||
public void Import(TModel item) => ModelStore.Add(item);
|
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>
|
/// <summary>
|
||||||
/// Delete an item from the manager.
|
/// Delete an item from the manager.
|
||||||
/// Is a no-op for already deleted items.
|
/// Is a no-op for already deleted items.
|
||||||
@ -180,14 +189,48 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restore all items that were previously deleted.
|
/// Delete multiple items.
|
||||||
/// This will post notifications tracking progress.
|
/// This will post notifications tracking progress.
|
||||||
/// </summary>
|
/// </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
|
var notification = new ProgressNotification
|
||||||
{
|
{
|
||||||
@ -200,16 +243,19 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
foreach (var item in deletedItems)
|
using (ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
if (notification.State == ProgressNotificationState.Cancelled)
|
if (notification.State == ProgressNotificationState.Cancelled)
|
||||||
// user requested abort
|
// user requested abort
|
||||||
return;
|
return;
|
||||||
|
|
||||||
notification.Text = $"Restoring ({i} of {deletedItems.Count})";
|
notification.Text = $"Restoring ({i} of {items.Count})";
|
||||||
notification.Progress = (float)++i / deletedItems.Count;
|
notification.Progress = (float)++i / items.Count;
|
||||||
Undelete(item);
|
Undelete(item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
notification.State = ProgressNotificationState.Completed;
|
notification.State = ProgressNotificationState.Completed;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform any common clean-up tasks. Should be run when idle, or whenever necessary.
|
/// Perform any common clean-up tasks. Should be run when idle, or whenever necessary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Cleanup()
|
public virtual void PurgeDeletable()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
@ -72,5 +75,37 @@ namespace osu.Game.Database
|
|||||||
ItemAdded?.Invoke(item);
|
ItemAdded?.Invoke(item);
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ namespace osu.Game.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Cleanup()
|
public override void PurgeDeletable()
|
||||||
{
|
{
|
||||||
using (var usage = ContextFactory.GetForWrite())
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
|
@ -172,7 +172,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
API.Register(this);
|
API.Register(this);
|
||||||
|
|
||||||
FileStore.Cleanup();
|
FileStore.PurgeDeletable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runMigrations()
|
private void runMigrations()
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
||||||
{
|
{
|
||||||
deleteButton.Enabled.Value = false;
|
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 = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
undeleteButton.Enabled.Value = false;
|
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));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user