2019-01-24 16:43:03 +08:00
// 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.
2018-04-13 17:19:50 +08:00
2021-09-30 15:45:32 +08:00
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 ;
2021-11-09 16:27:07 +08:00
using osu.Framework.Audio.Mixing ;
using osu.Framework.Audio.Track ;
2021-09-30 15:45:32 +08:00
using osu.Framework.IO.Stores ;
using osu.Framework.Platform ;
2020-10-16 13:39:02 +08:00
using osu.Framework.Testing ;
2021-09-30 15:45:32 +08:00
using osu.Game.Database ;
using osu.Game.IO ;
using osu.Game.IO.Archives ;
using osu.Game.Online.API ;
2021-11-04 17:02:44 +08:00
using osu.Game.Online.API.Requests.Responses ;
2021-09-30 15:45:32 +08:00
using osu.Game.Overlays.Notifications ;
using osu.Game.Rulesets ;
using osu.Game.Skinning ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Beatmaps
{
/// <summary>
2021-09-30 14:43:49 +08:00
/// Handles general operations related to global beatmap management.
2018-04-13 17:19:50 +08:00
/// </summary>
2020-10-16 13:39:02 +08:00
[ExcludeFromDynamicCompile]
2021-11-05 16:37:05 +08:00
public class BeatmapManager : IModelDownloader < IBeatmapSetInfo > , IModelManager < BeatmapSetInfo > , IModelFileManager < BeatmapSetInfo , BeatmapSetFileInfo > , IModelImporter < BeatmapSetInfo > , IWorkingBeatmapCache , IDisposable
2018-04-13 17:19:50 +08:00
{
2021-11-09 16:27:07 +08:00
public ITrackStore BeatmapTrackStore { get ; }
2021-09-30 15:45:32 +08:00
private readonly BeatmapModelManager beatmapModelManager ;
2021-09-30 17:21:16 +08:00
private readonly BeatmapModelDownloader beatmapModelDownloader ;
2021-09-30 15:45:32 +08:00
private readonly WorkingBeatmapCache workingBeatmapCache ;
2021-10-02 23:55:29 +08:00
private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue ;
2021-09-30 15:45:32 +08:00
2021-11-09 16:27:07 +08:00
public BeatmapManager ( Storage storage , IDatabaseContextFactory contextFactory , RulesetStore rulesets , IAPIProvider api , [ NotNull ] AudioManager audioManager , IResourceStore < byte [ ] > gameResources , GameHost host = null , WorkingBeatmap defaultBeatmap = null , bool performOnlineLookups = false , AudioMixer mainTrackMixer = null )
2018-04-13 17:19:50 +08:00
{
2021-11-09 16:27:07 +08:00
var userResources = new FileStore ( contextFactory , storage ) . Store ;
BeatmapTrackStore = audioManager . GetTrackStore ( userResources ) ;
2021-09-30 15:45:32 +08:00
beatmapModelManager = CreateBeatmapModelManager ( storage , contextFactory , rulesets , api , host ) ;
2021-09-30 17:21:16 +08:00
beatmapModelDownloader = CreateBeatmapModelDownloader ( beatmapModelManager , api , host ) ;
2021-11-09 16:27:07 +08:00
workingBeatmapCache = CreateWorkingBeatmapCache ( audioManager , gameResources , userResources , defaultBeatmap , host ) ;
2021-09-30 15:45:32 +08:00
workingBeatmapCache . BeatmapManager = beatmapModelManager ;
2021-10-06 11:05:30 +08:00
beatmapModelManager . WorkingBeatmapCache = workingBeatmapCache ;
2021-09-30 15:45:32 +08:00
2021-09-30 16:14:35 +08:00
if ( performOnlineLookups )
{
2021-10-02 23:55:29 +08:00
onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue ( api , storage ) ;
beatmapModelManager . OnlineLookupQueue = onlineBeatmapLookupQueue ;
2021-09-30 16:14:35 +08:00
}
2018-07-19 12:41:09 +08:00
}
2021-11-05 15:46:40 +08:00
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader ( IModelImporter < BeatmapSetInfo > modelManager , IAPIProvider api , GameHost host )
2021-09-30 17:21:16 +08:00
{
return new BeatmapModelDownloader ( modelManager , api , host ) ;
}
2021-11-09 16:27:07 +08:00
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 ) ;
}
2021-09-30 15:45:32 +08:00
protected virtual BeatmapModelManager CreateBeatmapModelManager ( Storage storage , IDatabaseContextFactory contextFactory , RulesetStore rulesets , IAPIProvider api , GameHost host ) = >
2021-09-30 17:21:16 +08:00
new BeatmapModelManager ( storage , contextFactory , rulesets , host ) ;
2021-09-30 15:45:32 +08:00
/// <summary>
/// Create a new <see cref="WorkingBeatmap"/>.
/// </summary>
2021-11-04 17:02:44 +08:00
public WorkingBeatmap CreateNew ( RulesetInfo ruleset , APIUser user )
2021-09-30 15:45:32 +08:00
{
var metadata = new BeatmapMetadata
{
Author = user ,
} ;
var set = new BeatmapSetInfo
{
Metadata = metadata ,
Beatmaps = new List < BeatmapInfo >
{
new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty ( ) ,
Ruleset = ruleset ,
Metadata = metadata ,
WidescreenStoryboard = true ,
SamplesMatchPlaybackRate = true ,
}
}
} ;
2021-09-30 18:33:12 +08:00
var imported = beatmapModelManager . Import ( set ) . Result . Value ;
return GetWorkingBeatmap ( imported . Beatmaps . First ( ) ) ;
2021-09-30 15:45:32 +08:00
}
#region Delegation to BeatmapModelManager ( methods which previously existed locally ) .
/// <summary>
/// Fired when a single difficulty has been hidden.
/// </summary>
2021-11-07 10:34:37 +08:00
public event Action < BeatmapInfo > BeatmapHidden
2021-11-05 17:05:31 +08:00
{
2021-11-07 10:34:37 +08:00
add = > beatmapModelManager . BeatmapHidden + = value ;
remove = > beatmapModelManager . BeatmapHidden - = value ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
/// <summary>
/// Fired when a single difficulty has been restored.
/// </summary>
2021-11-07 10:34:37 +08:00
public event Action < BeatmapInfo > BeatmapRestored
2021-11-05 17:05:31 +08:00
{
2021-11-07 10:34:37 +08:00
add = > beatmapModelManager . BeatmapRestored + = value ;
remove = > beatmapModelManager . BeatmapRestored - = value ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
/// <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>
2021-11-03 15:46:05 +08:00
public virtual void Save ( BeatmapInfo info , IBeatmap beatmapContent , ISkin beatmapSkin = null ) = >
beatmapModelManager . Save ( info , beatmapContent , beatmapSkin ) ;
2021-09-30 15:45:32 +08:00
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public List < BeatmapSetInfo > GetAllUsableBeatmapSets ( IncludedDetails includes = IncludedDetails . All , bool includeProtected = false ) = > beatmapModelManager . GetAllUsableBeatmapSets ( includes , includeProtected ) ;
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s. Note that files are not populated.
/// </summary>
/// <param name="includes">The level of detail to include in the returned objects.</param>
/// <param name="includeProtected">Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases.</param>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public IEnumerable < BeatmapSetInfo > GetAllUsableBeatmapSetsEnumerable ( IncludedDetails includes , bool includeProtected = false ) = > beatmapModelManager . GetAllUsableBeatmapSetsEnumerable ( includes , includeProtected ) ;
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="includes">The level of detail to include in the returned objects.</param>
/// <returns>Results from the provided query.</returns>
public IEnumerable < BeatmapSetInfo > QueryBeatmapSets ( Expression < Func < BeatmapSetInfo , bool > > query , IncludedDetails includes = IncludedDetails . All ) = > beatmapModelManager . QueryBeatmapSets ( query , includes ) ;
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>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 BeatmapSetInfo QueryBeatmapSet ( Expression < Func < BeatmapSetInfo , bool > > query ) = > beatmapModelManager . QueryBeatmapSet ( query ) ;
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>Results from the provided query.</returns>
public IQueryable < BeatmapInfo > QueryBeatmaps ( Expression < Func < BeatmapInfo , bool > > query ) = > beatmapModelManager . QueryBeatmaps ( query ) ;
/// <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 ) ;
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
public WorkingBeatmap DefaultBeatmap = > workingBeatmapCache . DefaultBeatmap ;
/// <summary>
/// Fired when a notification should be presented to the user.
/// </summary>
2021-09-30 17:21:16 +08:00
public Action < Notification > PostNotification
{
set
{
beatmapModelManager . PostNotification = value ;
beatmapModelDownloader . PostNotification = value ;
}
}
2021-09-30 15:45:32 +08:00
/// <summary>
/// Delete a beatmap difficulty.
/// </summary>
2021-10-02 23:55:29 +08:00
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
public void Hide ( BeatmapInfo beatmapInfo ) = > beatmapModelManager . Hide ( beatmapInfo ) ;
2021-09-30 15:45:32 +08:00
/// <summary>
/// Restore a beatmap difficulty.
/// </summary>
2021-10-02 23:55:29 +08:00
/// <param name="beatmapInfo">The beatmap difficulty to restore.</param>
public void Restore ( BeatmapInfo beatmapInfo ) = > beatmapModelManager . Restore ( beatmapInfo ) ;
2020-04-28 20:43:35 +08:00
2021-09-30 15:45:32 +08:00
#endregion
#region Implementation of IModelManager < BeatmapSetInfo >
2021-09-30 17:21:16 +08:00
public bool IsAvailableLocally ( BeatmapSetInfo model )
{
return beatmapModelManager . IsAvailableLocally ( model ) ;
}
2021-11-06 21:31:49 +08:00
public event Action < BeatmapSetInfo > ItemUpdated
2021-11-05 17:05:31 +08:00
{
2021-11-06 21:31:49 +08:00
add = > beatmapModelManager . ItemUpdated + = value ;
remove = > beatmapModelManager . ItemUpdated - = value ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
2021-11-06 21:31:49 +08:00
public event Action < BeatmapSetInfo > ItemRemoved
2021-11-05 17:05:31 +08:00
{
2021-11-06 21:31:49 +08:00
add = > beatmapModelManager . ItemRemoved + = value ;
remove = > beatmapModelManager . ItemRemoved - = value ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
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 < BeatmapSetInfo > items , bool silent = false )
{
beatmapModelManager . Delete ( items , silent ) ;
}
public void Undelete ( List < BeatmapSetInfo > items , bool silent = false )
{
beatmapModelManager . Undelete ( items , silent ) ;
}
public void Undelete ( BeatmapSetInfo item )
{
beatmapModelManager . Undelete ( item ) ;
}
#endregion
#region Implementation of IModelDownloader < BeatmapSetInfo >
2021-11-06 21:31:49 +08:00
public event Action < ArchiveDownloadRequest < IBeatmapSetInfo > > DownloadBegan
2021-11-05 17:05:31 +08:00
{
2021-11-06 21:31:49 +08:00
add = > beatmapModelDownloader . DownloadBegan + = value ;
remove = > beatmapModelDownloader . DownloadBegan - = value ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
2021-11-06 21:31:49 +08:00
public event Action < ArchiveDownloadRequest < IBeatmapSetInfo > > DownloadFailed
2021-11-05 17:05:31 +08:00
{
2021-11-06 21:31:49 +08:00
add = > beatmapModelDownloader . DownloadFailed + = value ;
remove = > beatmapModelDownloader . DownloadFailed - = value ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
2021-11-05 18:56:18 +08:00
public bool Download ( IBeatmapSetInfo model , bool minimiseDownloadSize = false ) = >
beatmapModelDownloader . Download ( model , minimiseDownloadSize ) ;
2021-09-30 15:45:32 +08:00
2021-11-05 18:56:18 +08:00
public ArchiveDownloadRequest < IBeatmapSetInfo > GetExistingDownload ( IBeatmapSetInfo model ) = >
beatmapModelDownloader . GetExistingDownload ( model ) ;
2021-09-30 15:45:32 +08:00
#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 ) ;
}
2021-09-30 18:33:12 +08:00
public Task < IEnumerable < ILive < BeatmapSetInfo > > > Import ( ProgressNotification notification , params ImportTask [ ] tasks )
2021-09-30 15:45:32 +08:00
{
return beatmapModelManager . Import ( notification , tasks ) ;
}
2021-09-30 18:33:12 +08:00
public Task < ILive < BeatmapSetInfo > > Import ( ImportTask task , bool lowPriority = false , CancellationToken cancellationToken = default )
2021-09-30 15:45:32 +08:00
{
return beatmapModelManager . Import ( task , lowPriority , cancellationToken ) ;
}
2021-09-30 18:33:12 +08:00
public Task < ILive < BeatmapSetInfo > > Import ( ArchiveReader archive , bool lowPriority = false , CancellationToken cancellationToken = default )
2021-09-30 15:45:32 +08:00
{
return beatmapModelManager . Import ( archive , lowPriority , cancellationToken ) ;
}
2021-09-30 18:33:12 +08:00
public Task < ILive < BeatmapSetInfo > > Import ( BeatmapSetInfo item , ArchiveReader archive = null , bool lowPriority = false , CancellationToken cancellationToken = default )
2021-09-30 15:45:32 +08:00
{
return beatmapModelManager . Import ( item , archive , lowPriority , cancellationToken ) ;
}
public IEnumerable < string > HandledExtensions = > beatmapModelManager . HandledExtensions ;
#endregion
#region Implementation of IWorkingBeatmapCache
public WorkingBeatmap GetWorkingBeatmap ( BeatmapInfo importedBeatmap ) = > workingBeatmapCache . GetWorkingBeatmap ( importedBeatmap ) ;
2021-10-06 11:05:30 +08:00
void IWorkingBeatmapCache . Invalidate ( BeatmapSetInfo beatmapSetInfo ) = > workingBeatmapCache . Invalidate ( beatmapSetInfo ) ;
void IWorkingBeatmapCache . Invalidate ( BeatmapInfo beatmapInfo ) = > workingBeatmapCache . Invalidate ( beatmapInfo ) ;
2021-09-30 15:45:32 +08:00
#endregion
#region Implementation of IModelFileManager < in BeatmapSetInfo , in BeatmapSetFileInfo >
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
2021-10-01 00:43:57 +08:00
#region Implementation of IDisposable
public void Dispose ( )
{
2021-10-02 23:55:29 +08:00
onlineBeatmapLookupQueue ? . Dispose ( ) ;
2021-10-01 00:43:57 +08:00
}
#endregion
2021-10-15 15:00:09 +08:00
#region Implementation of IPostImports < out BeatmapSetInfo >
public Action < IEnumerable < ILive < BeatmapSetInfo > > > PostImport
{
set = > beatmapModelManager . PostImport = value ;
}
#endregion
2021-09-30 15:45:32 +08:00
}
2018-04-13 17:19:50 +08:00
}