// 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.Audio.Track; 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.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Skinning; namespace osu.Game.Beatmaps { /// /// Handles general operations related to global beatmap management. /// [ExcludeFromDynamicCompile] public class BeatmapManager : IModelDownloader, IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable { public ITrackStore BeatmapTrackStore { get; } private readonly BeatmapModelManager beatmapModelManager; private readonly BeatmapModelDownloader beatmapModelDownloader; private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) { var userResources = new FileStore(contextFactory, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host); beatmapModelDownloader = CreateBeatmapModelDownloader(beatmapModelManager, api, host); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache.BeatmapManager = beatmapModelManager; beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; if (performOnlineLookups) { onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); beatmapModelManager.OnlineLookupQueue = onlineBeatmapLookupQueue; } } protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter modelManager, IAPIProvider api, GameHost host) { return new BeatmapModelDownloader(modelManager, api, host); } protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) { return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) => new BeatmapModelManager(storage, contextFactory, rulesets, host); /// /// Create a new . /// public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user) { var metadata = new BeatmapMetadata { Author = user, }; var set = new BeatmapSetInfo { Metadata = metadata, Beatmaps = { new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, Metadata = metadata, WidescreenStoryboard = true, SamplesMatchPlaybackRate = true, } } }; var imported = beatmapModelManager.Import(set).Result.Value; return GetWorkingBeatmap(imported.Beatmaps.First()); } #region Delegation to BeatmapModelManager (methods which previously existed locally). /// /// Fired when a single difficulty has been hidden. /// public event Action BeatmapHidden { add => beatmapModelManager.BeatmapHidden += value; remove => beatmapModelManager.BeatmapHidden -= value; } /// /// Fired when a single difficulty has been restored. /// public event Action BeatmapRestored { add => beatmapModelManager.BeatmapRestored += value; remove => beatmapModelManager.BeatmapRestored -= value; } /// /// 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 IWorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; /// /// Fired when a notification should be presented to the user. /// public Action PostNotification { set { beatmapModelManager.PostNotification = value; beatmapModelDownloader.PostNotification = value; } } /// /// Delete a beatmap difficulty. /// /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) => beatmapModelManager.Hide(beatmapInfo); /// /// Restore a beatmap difficulty. /// /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) => beatmapModelManager.Restore(beatmapInfo); #endregion #region Implementation of IModelManager public bool IsAvailableLocally(BeatmapSetInfo model) { return beatmapModelManager.IsAvailableLocally(model); } public event Action ItemUpdated { add => beatmapModelManager.ItemUpdated += value; remove => beatmapModelManager.ItemUpdated -= value; } public event Action ItemRemoved { add => beatmapModelManager.ItemRemoved += value; remove => beatmapModelManager.ItemRemoved -= value; } 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 event Action> DownloadBegan { add => beatmapModelDownloader.DownloadBegan += value; remove => beatmapModelDownloader.DownloadBegan -= value; } public event Action> DownloadFailed { add => beatmapModelDownloader.DownloadFailed += value; remove => beatmapModelDownloader.DownloadFailed -= value; } public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false) => beatmapModelDownloader.Download(model, minimiseDownloadSize); public ArchiveDownloadRequest GetExistingDownload(IBeatmapSetInfo model) => beatmapModelDownloader.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); void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); #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 #region Implementation of IDisposable public void Dispose() { onlineBeatmapLookupQueue?.Dispose(); } #endregion #region Implementation of IPostImports public Action>> PostImport { set => beatmapModelManager.PostImport = value; } #endregion } }