mirror of https://github.com/ppy/osu
Combine `BeatmapManager` and `BeatmapModelManager` into one class
This commit is contained in:
parent
8ea3042435
commit
1f3e1b2d97
|
@ -210,16 +210,16 @@ public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rules
|
|||
{
|
||||
}
|
||||
|
||||
protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
||||
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
||||
{
|
||||
return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue);
|
||||
return new TestBeatmapImporter(this, storage, realm, onlineLookupQueue);
|
||||
}
|
||||
|
||||
internal class TestBeatmapModelManager : BeatmapModelManager
|
||||
internal class TestBeatmapImporter : BeatmapImporter
|
||||
{
|
||||
private readonly TestBeatmapManager testBeatmapManager;
|
||||
|
||||
public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
||||
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
||||
: base(databaseAccess, storage, beatmapOnlineLookupQueue)
|
||||
{
|
||||
this.testBeatmapManager = testBeatmapManager;
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
|
@ -23,8 +30,6 @@
|
|||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -35,14 +40,15 @@ public class BeatmapManager : IModelManager<BeatmapSetInfo>, IModelFileManager<B
|
|||
{
|
||||
public ITrackStore BeatmapTrackStore { get; }
|
||||
|
||||
private readonly BeatmapModelManager beatmapModelManager;
|
||||
private readonly BeatmapImporter beatmapImporter;
|
||||
|
||||
private readonly WorkingBeatmapCache workingBeatmapCache;
|
||||
private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue;
|
||||
|
||||
private readonly RealmAccess realm;
|
||||
|
||||
public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
|
||||
public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null,
|
||||
WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
|
||||
{
|
||||
this.realm = realm;
|
||||
|
||||
|
@ -58,19 +64,18 @@ public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets,
|
|||
|
||||
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
||||
|
||||
beatmapModelManager = CreateBeatmapModelManager(storage, realm, rulesets, onlineBeatmapLookupQueue);
|
||||
beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue);
|
||||
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
||||
|
||||
beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache;
|
||||
}
|
||||
|
||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap? defaultBeatmap, GameHost? host)
|
||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap? defaultBeatmap,
|
||||
GameHost? host)
|
||||
{
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||
}
|
||||
|
||||
protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) =>
|
||||
new BeatmapModelManager(realm, storage, onlineLookupQueue);
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) =>
|
||||
new BeatmapImporter(realm, storage, onlineLookupQueue);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,
|
||||
|
@ -99,7 +104,7 @@ public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user)
|
|||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
||||
b.BeatmapSet = beatmapSet;
|
||||
|
||||
var imported = beatmapModelManager.Import(beatmapSet);
|
||||
var imported = beatmapImporter.Import(beatmapSet);
|
||||
|
||||
if (imported == null)
|
||||
throw new InvalidOperationException("Failed to import new beatmap");
|
||||
|
@ -169,12 +174,12 @@ public virtual WorkingBeatmap CopyExistingDifficulty(BeatmapSetInfo targetBeatma
|
|||
private WorkingBeatmap addDifficultyToSet(BeatmapSetInfo targetBeatmapSet, IBeatmap newBeatmap, ISkin beatmapSkin)
|
||||
{
|
||||
// populate circular beatmap set info <-> beatmap info references manually.
|
||||
// several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()`
|
||||
// several places like `BeatmapImporter.Save()` or `GetWorkingBeatmap()`
|
||||
// rely on them being freely traversable in both directions for correct operation.
|
||||
targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo);
|
||||
newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet;
|
||||
|
||||
beatmapModelManager.Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin);
|
||||
Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin);
|
||||
|
||||
workingBeatmapCache.Invalidate(targetBeatmapSet);
|
||||
return GetWorkingBeatmap(newBeatmap.BeatmapInfo);
|
||||
|
@ -255,23 +260,14 @@ public List<BeatmapSetInfo> GetAllUsableBeatmapSets()
|
|||
return realm.Run(r => r.All<BeatmapSetInfo>().FirstOrDefault(query)?.ToLive(realm));
|
||||
}
|
||||
|
||||
#region Delegation to BeatmapModelManager (methods which previously existed locally).
|
||||
#region Delegation to BeatmapImporter (methods which previously existed locally).
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query) => beatmapModelManager.QueryBeatmap(query)?.Detach();
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IBeatmap"/> file against a given <see cref="BeatmapInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
|
||||
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
|
||||
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
||||
public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin? beatmapSkin = null) =>
|
||||
beatmapModelManager.Save(info, beatmapContent, beatmapSkin);
|
||||
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query) => realm.Run(r => r.All<BeatmapInfo>().FirstOrDefault(query)?.Detach());
|
||||
|
||||
/// <summary>
|
||||
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
|
||||
|
@ -281,9 +277,10 @@ public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin? beatm
|
|||
/// <summary>
|
||||
/// Fired when a notification should be presented to the user.
|
||||
/// </summary>
|
||||
public Action<Notification> PostNotification
|
||||
public Action<Notification>? PostNotification
|
||||
{
|
||||
set => beatmapModelManager.PostNotification = value;
|
||||
get => beatmapImporter.PostNotification;
|
||||
set => beatmapImporter.PostNotification = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -292,17 +289,17 @@ public Action<Notification> PostNotification
|
|||
|
||||
public bool IsAvailableLocally(BeatmapSetInfo model)
|
||||
{
|
||||
return beatmapModelManager.IsAvailableLocally(model);
|
||||
return beatmapImporter.IsAvailableLocally(model);
|
||||
}
|
||||
|
||||
public bool Delete(BeatmapSetInfo item)
|
||||
{
|
||||
return beatmapModelManager.Delete(item);
|
||||
return beatmapImporter.Delete(item);
|
||||
}
|
||||
|
||||
public void Delete(List<BeatmapSetInfo> items, bool silent = false)
|
||||
{
|
||||
beatmapModelManager.Delete(items, silent);
|
||||
beatmapImporter.Delete(items, silent);
|
||||
}
|
||||
|
||||
public void Delete(Expression<Func<BeatmapSetInfo, bool>>? filter = null, bool silent = false)
|
||||
|
@ -314,51 +311,156 @@ public void Delete(Expression<Func<BeatmapSetInfo, bool>>? filter = null, bool s
|
|||
if (filter != null)
|
||||
items = items.Where(filter);
|
||||
|
||||
beatmapModelManager.Delete(items.ToList(), silent);
|
||||
beatmapImporter.Delete(items.ToList(), silent);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IBeatmap"/> file against a given <see cref="BeatmapInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
|
||||
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
|
||||
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
||||
public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null)
|
||||
{
|
||||
var setInfo = beatmapInfo.BeatmapSet;
|
||||
Debug.Assert(setInfo != null);
|
||||
|
||||
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`.
|
||||
// This should hopefully be temporary, assuming said clone is eventually removed.
|
||||
|
||||
// Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved)
|
||||
// *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation).
|
||||
// CopyTo() will undo such adjustments, while CopyFrom() will not.
|
||||
beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty);
|
||||
|
||||
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
|
||||
beatmapContent.BeatmapInfo = beatmapInfo;
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
|
||||
var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase));
|
||||
string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);
|
||||
|
||||
// ensure that two difficulties from the set don't point at the same beatmap file.
|
||||
if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase)))
|
||||
throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'.");
|
||||
|
||||
if (existingFileInfo != null)
|
||||
DeleteFile(setInfo, existingFileInfo);
|
||||
|
||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||
beatmapInfo.Hash = stream.ComputeSHA2Hash();
|
||||
|
||||
AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));
|
||||
|
||||
realm.Write(r => setInfo.CopyChangesToRealm(r.Find<BeatmapSetInfo>(setInfo.ID)));
|
||||
}
|
||||
|
||||
workingBeatmapCache.Invalidate(beatmapInfo);
|
||||
|
||||
static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
var metadata = beatmapInfo.Metadata;
|
||||
return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename();
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteAllVideos()
|
||||
{
|
||||
realm.Write(r =>
|
||||
{
|
||||
var items = r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
||||
beatmapModelManager.DeleteVideos(items.ToList());
|
||||
DeleteVideos(items.ToList());
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete videos from a list of beatmaps.
|
||||
/// This will post notifications tracking progress.
|
||||
/// </summary>
|
||||
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
|
||||
{
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var notification = new ProgressNotification
|
||||
{
|
||||
Progress = 0,
|
||||
Text = $"Preparing to delete all {beatmapImporter.HumanisedModelName} videos...",
|
||||
CompletionText = "No videos found to delete!",
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
if (!silent)
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
int i = 0;
|
||||
int deleted = 0;
|
||||
|
||||
foreach (var b in items)
|
||||
{
|
||||
if (notification.State == ProgressNotificationState.Cancelled)
|
||||
// user requested abort
|
||||
return;
|
||||
|
||||
var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal)));
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
DeleteFile(b, video);
|
||||
deleted++;
|
||||
notification.CompletionText = $"Deleted {deleted} {beatmapImporter.HumanisedModelName} video(s)!";
|
||||
}
|
||||
|
||||
notification.Text = $"Deleting videos from {beatmapImporter.HumanisedModelName}s ({deleted} deleted)";
|
||||
|
||||
notification.Progress = (float)++i / items.Count;
|
||||
}
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
||||
public void UndeleteAll()
|
||||
{
|
||||
realm.Run(r => beatmapModelManager.Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||
realm.Run(r => beatmapImporter.Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||
}
|
||||
|
||||
public void Undelete(List<BeatmapSetInfo> items, bool silent = false)
|
||||
{
|
||||
beatmapModelManager.Undelete(items, silent);
|
||||
beatmapImporter.Undelete(items, silent);
|
||||
}
|
||||
|
||||
public void Undelete(BeatmapSetInfo item)
|
||||
{
|
||||
beatmapModelManager.Undelete(item);
|
||||
beatmapImporter.Undelete(item);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICanAcceptFiles
|
||||
|
||||
public Task Import(params string[] paths) => beatmapModelManager.Import(paths);
|
||||
public Task Import(params string[] paths) => beatmapImporter.Import(paths);
|
||||
|
||||
public Task Import(params ImportTask[] tasks) => beatmapModelManager.Import(tasks);
|
||||
public Task Import(params ImportTask[] tasks) => beatmapImporter.Import(tasks);
|
||||
|
||||
public Task<IEnumerable<Live<BeatmapSetInfo>>> Import(ProgressNotification notification, params ImportTask[] tasks) => beatmapModelManager.Import(notification, tasks);
|
||||
public Task<IEnumerable<Live<BeatmapSetInfo>>> Import(ProgressNotification notification, params ImportTask[] tasks) => beatmapImporter.Import(notification, tasks);
|
||||
|
||||
public Task<Live<BeatmapSetInfo>?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => beatmapModelManager.Import(task, batchImport, cancellationToken);
|
||||
public Task<Live<BeatmapSetInfo>?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) =>
|
||||
beatmapImporter.Import(task, batchImport, cancellationToken);
|
||||
|
||||
public Task<Live<BeatmapSetInfo>?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) => beatmapModelManager.Import(archive, batchImport, cancellationToken);
|
||||
public Task<Live<BeatmapSetInfo>?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) =>
|
||||
beatmapImporter.Import(archive, batchImport, cancellationToken);
|
||||
|
||||
public Live<BeatmapSetInfo>? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) => beatmapModelManager.Import(item, archive, false, cancellationToken);
|
||||
public Live<BeatmapSetInfo>? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) =>
|
||||
beatmapImporter.Import(item, archive, false, cancellationToken);
|
||||
|
||||
public IEnumerable<string> HandledExtensions => beatmapModelManager.HandledExtensions;
|
||||
public IEnumerable<string> HandledExtensions => beatmapImporter.HandledExtensions;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -400,17 +502,17 @@ public WorkingBeatmap GetWorkingBeatmap(Live<BeatmapInfo>? importedBeatmap)
|
|||
|
||||
public void ReplaceFile(BeatmapSetInfo model, RealmNamedFileUsage file, Stream contents)
|
||||
{
|
||||
beatmapModelManager.ReplaceFile(model, file, contents);
|
||||
beatmapImporter.ReplaceFile(model, file, contents);
|
||||
}
|
||||
|
||||
public void DeleteFile(BeatmapSetInfo model, RealmNamedFileUsage file)
|
||||
{
|
||||
beatmapModelManager.DeleteFile(model, file);
|
||||
beatmapImporter.DeleteFile(model, file);
|
||||
}
|
||||
|
||||
public void AddFile(BeatmapSetInfo model, Stream contents, string filename)
|
||||
{
|
||||
beatmapModelManager.AddFile(model, contents, filename);
|
||||
beatmapImporter.AddFile(model, contents, filename);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -428,7 +530,7 @@ public void Dispose()
|
|||
|
||||
public Action<IEnumerable<Live<BeatmapSetInfo>>>? PostImport
|
||||
{
|
||||
set => beatmapModelManager.PostImport = value;
|
||||
set => beatmapImporter.PostImport = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class BeatmapModelManager : BeatmapImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// The game working beatmap cache, used to invalidate entries on changes.
|
||||
/// </summary>
|
||||
public IWorkingBeatmapCache? WorkingBeatmapCache { private get; set; }
|
||||
|
||||
public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
|
||||
: base(realm, storage, onlineLookupQueue)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IBeatmap"/> file against a given <see cref="BeatmapInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
|
||||
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
|
||||
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
||||
public void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null)
|
||||
{
|
||||
var setInfo = beatmapInfo.BeatmapSet;
|
||||
Debug.Assert(setInfo != null);
|
||||
|
||||
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`.
|
||||
// This should hopefully be temporary, assuming said clone is eventually removed.
|
||||
|
||||
// Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved)
|
||||
// *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation).
|
||||
// CopyTo() will undo such adjustments, while CopyFrom() will not.
|
||||
beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty);
|
||||
|
||||
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
|
||||
beatmapContent.BeatmapInfo = beatmapInfo;
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
|
||||
var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase));
|
||||
string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);
|
||||
|
||||
// ensure that two difficulties from the set don't point at the same beatmap file.
|
||||
if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase)))
|
||||
throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'.");
|
||||
|
||||
if (existingFileInfo != null)
|
||||
DeleteFile(setInfo, existingFileInfo);
|
||||
|
||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||
beatmapInfo.Hash = stream.ComputeSHA2Hash();
|
||||
|
||||
AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));
|
||||
|
||||
Realm.Write(r => setInfo.CopyChangesToRealm(r.Find<BeatmapSetInfo>(setInfo.ID)));
|
||||
}
|
||||
|
||||
WorkingBeatmapCache?.Invalidate(beatmapInfo);
|
||||
|
||||
static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
var metadata = beatmapInfo.Metadata;
|
||||
return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query)
|
||||
{
|
||||
return Realm.Run(realm => realm.All<BeatmapInfo>().FirstOrDefault(query)?.Detach());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete videos from a list of beatmaps.
|
||||
/// This will post notifications tracking progress.
|
||||
/// </summary>
|
||||
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
|
||||
{
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var notification = new ProgressNotification
|
||||
{
|
||||
Progress = 0,
|
||||
Text = $"Preparing to delete all {HumanisedModelName} videos...",
|
||||
CompletionText = "No videos found to delete!",
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
if (!silent)
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
int i = 0;
|
||||
int deleted = 0;
|
||||
|
||||
foreach (var b in items)
|
||||
{
|
||||
if (notification.State == ProgressNotificationState.Cancelled)
|
||||
// user requested abort
|
||||
return;
|
||||
|
||||
var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal)));
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
DeleteFile(b, video);
|
||||
deleted++;
|
||||
notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!";
|
||||
}
|
||||
|
||||
notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)";
|
||||
|
||||
notification.Progress = (float)++i / items.Count;
|
||||
}
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,7 +72,7 @@ public abstract class RealmArchiveModelImporter<TModel> : IModelImporter<TModel>
|
|||
/// <summary>
|
||||
/// Set an endpoint for notifications to be posted to.
|
||||
/// </summary>
|
||||
public Action<Notification>? PostNotification { protected get; set; }
|
||||
public Action<Notification>? PostNotification { get; set; }
|
||||
|
||||
protected RealmArchiveModelImporter(Storage storage, RealmAccess realm)
|
||||
{
|
||||
|
|
|
@ -145,9 +145,9 @@ public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rules
|
|||
{
|
||||
}
|
||||
|
||||
protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
||||
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
||||
{
|
||||
return new BeatmapModelManager(realm, storage, onlineLookupQueue);
|
||||
return new BeatmapImporter(realm, storage, onlineLookupQueue);
|
||||
}
|
||||
|
||||
protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap defaultBeatmap, GameHost host)
|
||||
|
|
Loading…
Reference in New Issue