From 90225f20820ed74fe6269a7c1c4105d2c3e4866a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Sep 2021 16:45:32 +0900 Subject: [PATCH] Hook up all required interfaces to new `BeatmapManager` --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 26 +- osu.Game/Beatmaps/BeatmapManager.cs | 297 +++++++++++++++++- osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 15 + osu.Game/Beatmaps/WorkingBeatmapCache.cs | 42 +-- osu.Game/OsuGameBase.cs | 6 - osu.Game/Tests/Visual/EditorTestScene.cs | 37 ++- 6 files changed, 364 insertions(+), 59 deletions(-) create mode 100644 osu.Game/Beatmaps/IWorkingBeatmapCache.cs diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index a7d34fadbe..1a3f9e414d 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -158,18 +158,34 @@ namespace osu.Game.Tests.Online public Task CurrentImportTask { get; private set; } - protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) - => new TestDownloadRequest(set); + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) + { + return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host); + } public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) { } - public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + internal class TestBeatmapModelManager : BeatmapModelManager { - await AllowImport.Task.ConfigureAwait(false); - return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); + private readonly TestBeatmapManager testBeatmapManager; + + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, IDatabaseContextFactory databaseContextFactory, RulesetStore rulesetStore, IAPIProvider apiProvider, GameHost gameHost) + : base(storage, databaseContextFactory, rulesetStore, apiProvider, gameHost) + { + this.testBeatmapManager = testBeatmapManager; + } + + protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) + => new TestDownloadRequest(set); + + public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + { + await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); + return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); + } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c445925a90..18513945e5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -1,7 +1,27 @@ // Copyright (c) ppy Pty Ltd . 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.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Database; +using osu.Game.IO; +using osu.Game.IO.Archives; +using osu.Game.Online.API; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Users; namespace osu.Game.Beatmaps { @@ -9,13 +29,282 @@ namespace osu.Game.Beatmaps /// Handles general operations related to global beatmap management. /// [ExcludeFromDynamicCompile] - public class BeatmapManager + public class BeatmapManager : IModelDownloader, IModelFileManager, ICanAcceptFiles, IWorkingBeatmapCache { - public BeatmapManager() + private readonly BeatmapModelManager beatmapModelManager; + private readonly WorkingBeatmapCache workingBeatmapCache; + + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, + WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) { - beatmapModelManager = new BeatmapModelManager() + beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host); + workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, resources, new FileStore(contextFactory, storage).Store, defaultBeatmap, host); + + workingBeatmapCache.BeatmapManager = beatmapModelManager; + + var onlineBeatmapLookupCache = new BeatmapOnlineLookupQueue(api, storage); + + beatmapModelManager.PopulateOnlineInformation = onlineBeatmapLookupCache.UpdateAsync; } - } + protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) => + new WorkingBeatmapCache(audioManager, resources, storage, defaultBeatmap, host); + protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) => + new BeatmapModelManager(storage, contextFactory, rulesets, api, host); + + /// + /// Create a new . + /// + public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user) + { + var metadata = new BeatmapMetadata + { + Author = user, + }; + + var set = new BeatmapSetInfo + { + Metadata = metadata, + Beatmaps = new List + { + new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + Ruleset = ruleset, + Metadata = metadata, + WidescreenStoryboard = true, + SamplesMatchPlaybackRate = true, + } + } + }; + + var working = beatmapModelManager.Import(set).Result; + return GetWorkingBeatmap(working.Beatmaps.First()); + } + + #region Delegation to BeatmapModelManager (methods which previously existed locally). + + /// + /// Fired when a single difficulty has been hidden. + /// + public IBindable> BeatmapHidden => beatmapModelManager.BeatmapHidden; + + /// + /// Fired when a single difficulty has been restored. + /// + public IBindable> BeatmapRestored => beatmapModelManager.BeatmapRestored; + + /// + /// Saves an file against a given . + /// + /// The to save the content against. The file referenced by will be replaced. + /// The content to write. + /// The beatmap content to write, null if to be omitted. + public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) => beatmapModelManager.Save(info, beatmapContent, beatmapSkin); + + /// + /// Returns a list of all usable s. + /// + /// A list of available . + public List GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All, bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSets(includes, includeProtected); + + /// + /// Returns a list of all usable s. Note that files are not populated. + /// + /// The level of detail to include in the returned objects. + /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. + /// A list of available . + public IEnumerable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes, bool includeProtected = false) => beatmapModelManager.GetAllUsableBeatmapSetsEnumerable(includes, includeProtected); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The level of detail to include in the returned objects. + /// Results from the provided query. + public IEnumerable QueryBeatmapSets(Expression> query, IncludedDetails includes = IncludedDetails.All) => beatmapModelManager.QueryBeatmapSets(query, includes); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmapModelManager.QueryBeatmapSet(query); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// Results from the provided query. + public IQueryable QueryBeatmaps(Expression> query) => beatmapModelManager.QueryBeatmaps(query); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public BeatmapInfo QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query); + + /// + /// A default representation of a WorkingBeatmap to use when no beatmap is available. + /// + public WorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; + + /// + /// Fired when a notification should be presented to the user. + /// + public Action PostNotification { set => beatmapModelManager.PostNotification = value; } + + /// + /// Fired when the user requests to view the resulting import. + /// + public Action> PresentImport { set => beatmapModelManager.PresentImport = value; } + + /// + /// Delete a beatmap difficulty. + /// + /// The beatmap difficulty to hide. + public void Hide(BeatmapInfo beatmap) => beatmapModelManager.Hide(beatmap); + + /// + /// Restore a beatmap difficulty. + /// + /// The beatmap difficulty to restore. + public void Restore(BeatmapInfo beatmap) => beatmapModelManager.Restore(beatmap); + + #endregion + + #region Implementation of IModelManager + + public IBindable> ItemUpdated => beatmapModelManager.ItemUpdated; + + public IBindable> ItemRemoved => beatmapModelManager.ItemRemoved; + + public Task ImportFromStableAsync(StableStorage stableStorage) + { + return beatmapModelManager.ImportFromStableAsync(stableStorage); + } + + public void Export(BeatmapSetInfo item) + { + beatmapModelManager.Export(item); + } + + public void ExportModelTo(BeatmapSetInfo model, Stream outputStream) + { + beatmapModelManager.ExportModelTo(model, outputStream); + } + + public void Update(BeatmapSetInfo item) + { + beatmapModelManager.Update(item); + } + + public bool Delete(BeatmapSetInfo item) + { + return beatmapModelManager.Delete(item); + } + + public void Delete(List items, bool silent = false) + { + beatmapModelManager.Delete(items, silent); + } + + public void Undelete(List items, bool silent = false) + { + beatmapModelManager.Undelete(items, silent); + } + + public void Undelete(BeatmapSetInfo item) + { + beatmapModelManager.Undelete(item); + } + + #endregion + + #region Implementation of IModelDownloader + + public IBindable>> DownloadBegan => beatmapModelManager.DownloadBegan; + + public IBindable>> DownloadFailed => beatmapModelManager.DownloadFailed; + + public bool IsAvailableLocally(BeatmapSetInfo model) + { + return beatmapModelManager.IsAvailableLocally(model); + } + + public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false) + { + return beatmapModelManager.Download(model, minimiseDownloadSize); + } + + public ArchiveDownloadRequest GetExistingDownload(BeatmapSetInfo model) + { + return beatmapModelManager.GetExistingDownload(model); + } + + #endregion + + #region Implementation of ICanAcceptFiles + + public Task Import(params string[] paths) + { + return beatmapModelManager.Import(paths); + } + + public Task Import(params ImportTask[] tasks) + { + return beatmapModelManager.Import(tasks); + } + + public Task> Import(ProgressNotification notification, params ImportTask[] tasks) + { + return beatmapModelManager.Import(notification, tasks); + } + + public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + { + return beatmapModelManager.Import(task, lowPriority, cancellationToken); + } + + public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + { + return beatmapModelManager.Import(archive, lowPriority, cancellationToken); + } + + public Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + { + return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); + } + + public IEnumerable HandledExtensions => beatmapModelManager.HandledExtensions; + + #endregion + + #region Implementation of IWorkingBeatmapCache + + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + + #endregion + + #region Implementation of IModelFileManager + + public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents, string filename = null) + { + beatmapModelManager.ReplaceFile(model, file, contents, filename); + } + + public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file) + { + beatmapModelManager.DeleteFile(model, file); + } + + public void AddFile(BeatmapSetInfo model, Stream contents, string filename) + { + beatmapModelManager.AddFile(model, contents, filename); + } + + #endregion + } } diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs new file mode 100644 index 0000000000..881e734292 --- /dev/null +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Beatmaps +{ + public interface IWorkingBeatmapCache + { + /// + /// Retrieve a instance for the provided + /// + /// The beatmap to lookup. + /// A instance correlating to the provided . + WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo); + } +} diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 9f40eb4898..e117f1b82f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using JetBrains.Annotations; @@ -17,14 +16,12 @@ using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Storyboards; -using osu.Game.Users; namespace osu.Game.Beatmaps { - public class WorkingBeatmapCache : IBeatmapResourceProvider + public class WorkingBeatmapCache : IBeatmapResourceProvider, IWorkingBeatmapCache { private readonly WeakList workingCache = new WeakList(); @@ -33,7 +30,7 @@ namespace osu.Game.Beatmaps /// public readonly WorkingBeatmap DefaultBeatmap; - public BeatmapManager BeatmapManager { private get; set; } + public BeatmapModelManager BeatmapManager { private get; set; } private readonly AudioManager audioManager; private readonly IResourceStore resources; @@ -74,41 +71,6 @@ namespace osu.Game.Beatmaps } } - /// - /// Create a new . - /// - public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user) - { - var metadata = new BeatmapMetadata - { - Author = user, - }; - - var set = new BeatmapSetInfo - { - Metadata = metadata, - Beatmaps = new List - { - new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - Ruleset = ruleset, - Metadata = metadata, - WidescreenStoryboard = true, - SamplesMatchPlaybackRate = true, - } - } - }; - - var working = BeatmapManager.Import(set).Result; - return GetWorkingBeatmap(working.Beatmaps.First()); - } - - /// - /// Retrieve a instance for the provided - /// - /// The beatmap to lookup. - /// A instance correlating to the provided . public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) { // if there are no files, presume the full beatmap info has not yet been fetched from the database. diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8263e26dec..dc1cb7a850 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -138,8 +138,6 @@ namespace osu.Game private UserLookupCache userCache; - private BeatmapOnlineLookupQueue onlineBeatmapLookupCache; - private FileStore fileStore; private RulesetConfigCache rulesetConfigCache; @@ -246,10 +244,6 @@ namespace osu.Game dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap)); - onlineBeatmapLookupCache = new BeatmapOnlineLookupQueue(API, Storage); - - BeatmapManager.PopulateOnlineInformation = onlineBeatmapLookupCache.UpdateAsync; - // this should likely be moved to ArchiveModelManager when another case appears where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 1e26036116..ac8773a840 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -123,11 +123,40 @@ namespace osu.Game.Tests.Visual this.testBeatmap = testBeatmap; } - protected override string ComputeHash(BeatmapSetInfo item, ArchiveReader reader = null) - => string.Empty; + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) + { + return new TestBeatmapModelManager(storage, contextFactory, rulesets, api, host); + } - public override WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) - => testBeatmap; + protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) + { + return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host); + } + + private class TestWorkingBeatmapCache : WorkingBeatmapCache + { + private readonly TestBeatmapManager testBeatmapManager; + + public TestWorkingBeatmapCache(TestBeatmapManager testBeatmapManager, AudioManager audioManager, IResourceStore resourceStore, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost gameHost) + : base(audioManager, resourceStore, storage, defaultBeatmap, gameHost) + { + this.testBeatmapManager = testBeatmapManager; + } + + public override WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) + => testBeatmapManager.testBeatmap; + } + + internal class TestBeatmapModelManager : BeatmapModelManager + { + public TestBeatmapModelManager(Storage storage, IDatabaseContextFactory databaseContextFactory, RulesetStore rulesetStore, IAPIProvider apiProvider, GameHost gameHost) + : base(storage, databaseContextFactory, rulesetStore, apiProvider, gameHost) + { + } + + protected override string ComputeHash(BeatmapSetInfo item, ArchiveReader reader = null) + => string.Empty; + } public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) {