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 ;
2022-06-16 17:07:04 +08:00
using System.Diagnostics ;
2021-09-30 15:45:32 +08:00
using System.IO ;
using System.Linq ;
using System.Linq.Expressions ;
2022-06-16 17:07:04 +08:00
using System.Text ;
2021-09-30 15:45:32 +08:00
using System.Threading ;
using System.Threading.Tasks ;
using osu.Framework.Audio ;
2021-11-09 16:27:07 +08:00
using osu.Framework.Audio.Track ;
2022-06-16 17:07:04 +08:00
using osu.Framework.Extensions ;
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 ;
2022-06-16 17:07:04 +08:00
using osu.Game.Beatmaps.Formats ;
2021-09-30 15:45:32 +08:00
using osu.Game.Database ;
2022-06-16 17:07:04 +08:00
using osu.Game.Extensions ;
2021-09-30 15:45:32 +08:00
using osu.Game.IO.Archives ;
2021-11-22 14:52:55 +08:00
using osu.Game.Models ;
2021-09-30 15:45:32 +08:00
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 ;
2022-02-17 06:12:13 +08:00
using osu.Game.Utils ;
2018-04-13 17:19:50 +08:00
2017-07-27 15:56:41 +08:00
namespace osu.Game.Beatmaps
{
/// <summary>
2021-09-30 14:43:49 +08:00
/// Handles general operations related to global beatmap management.
2017-07-27 15:56:41 +08:00
/// </summary>
2020-10-16 13:39:02 +08:00
[ExcludeFromDynamicCompile]
2022-06-16 17:53:13 +08:00
public class BeatmapManager : ModelManager < BeatmapSetInfo > , IModelImporter < BeatmapSetInfo > , IWorkingBeatmapCache , IDisposable
2017-07-27 15:56:41 +08:00
{
2021-11-09 16:27:07 +08:00
public ITrackStore BeatmapTrackStore { get ; }
2022-06-16 17:07:04 +08:00
private readonly BeatmapImporter beatmapImporter ;
2021-09-30 17:21:16 +08:00
2021-09-30 15:45:32 +08:00
private readonly WorkingBeatmapCache workingBeatmapCache ;
2021-12-15 13:26:38 +08:00
private readonly BeatmapOnlineLookupQueue ? onlineBeatmapLookupQueue ;
2022-06-16 17:07:04 +08:00
public BeatmapManager ( Storage storage , RealmAccess realm , RulesetStore rulesets , IAPIProvider ? api , AudioManager audioManager , IResourceStore < byte [ ] > gameResources , GameHost ? host = null ,
WorkingBeatmap ? defaultBeatmap = null , bool performOnlineLookups = false )
2022-06-16 17:53:13 +08:00
: base ( storage , realm )
2017-07-27 15:56:41 +08:00
{
2021-12-14 18:47:11 +08:00
if ( performOnlineLookups )
2022-01-11 20:36:34 +08:00
{
if ( api = = null )
throw new ArgumentNullException ( nameof ( api ) , "API must be provided if online lookups are required." ) ;
2021-12-14 18:47:11 +08:00
onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue ( api , storage ) ;
2022-01-11 20:36:34 +08:00
}
2021-12-14 18:47:11 +08:00
2022-01-24 18:59:58 +08:00
var userResources = new RealmFileStore ( realm , storage ) . Store ;
2021-11-09 16:27:07 +08:00
BeatmapTrackStore = audioManager . GetTrackStore ( userResources ) ;
2022-06-16 17:07:04 +08:00
beatmapImporter = CreateBeatmapImporter ( storage , realm , rulesets , onlineBeatmapLookupQueue ) ;
2022-06-16 18:48:18 +08:00
beatmapImporter . PostNotification = obj = > PostNotification ? . Invoke ( obj ) ;
2021-11-09 16:27:07 +08:00
workingBeatmapCache = CreateWorkingBeatmapCache ( audioManager , gameResources , userResources , defaultBeatmap , host ) ;
2018-07-19 12:41:09 +08:00
}
2022-06-16 17:07:04 +08:00
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache ( AudioManager audioManager , IResourceStore < byte [ ] > resources , IResourceStore < byte [ ] > storage , WorkingBeatmap ? defaultBeatmap ,
GameHost ? host )
2021-11-09 16:27:07 +08:00
{
return new WorkingBeatmapCache ( BeatmapTrackStore , audioManager , resources , storage , defaultBeatmap , host ) ;
}
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
protected virtual BeatmapImporter CreateBeatmapImporter ( Storage storage , RealmAccess realm , RulesetStore rulesets , BeatmapOnlineLookupQueue ? onlineLookupQueue ) = >
2022-06-16 17:53:13 +08:00
new BeatmapImporter ( storage , realm , onlineLookupQueue ) ;
2021-09-30 15:45:32 +08:00
/// <summary>
2022-01-24 00:49:17 +08:00
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,
/// with a single difficulty which is backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned usable <see cref="WorkingBeatmap"/>.
2021-09-30 15:45:32 +08:00
/// </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
{
2021-11-22 14:52:55 +08:00
Author = new RealmUser
{
OnlineID = user . OnlineID ,
Username = user . Username ,
}
2021-09-30 15:45:32 +08:00
} ;
2022-01-11 17:17:13 +08:00
var beatmapSet = new BeatmapSetInfo
2021-09-30 15:45:32 +08:00
{
2021-11-24 12:25:52 +08:00
Beatmaps =
2021-09-30 15:45:32 +08:00
{
2022-01-28 04:41:30 +08:00
new BeatmapInfo ( ruleset , new BeatmapDifficulty ( ) , metadata )
2021-09-30 15:45:32 +08:00
}
} ;
2022-01-11 17:17:13 +08:00
foreach ( BeatmapInfo b in beatmapSet . Beatmaps )
b . BeatmapSet = beatmapSet ;
2022-06-16 17:07:04 +08:00
var imported = beatmapImporter . Import ( beatmapSet ) ;
2021-09-30 18:33:12 +08:00
2022-01-11 15:30:55 +08:00
if ( imported = = null )
throw new InvalidOperationException ( "Failed to import new beatmap" ) ;
return imported . PerformRead ( s = > GetWorkingBeatmap ( s . Beatmaps . First ( ) ) ) ;
2021-09-30 15:45:32 +08:00
}
2022-01-24 00:49:17 +08:00
/// <summary>
2022-02-15 03:56:05 +08:00
/// Add a new difficulty to the provided <paramref name="targetBeatmapSet"/> based on the provided <paramref name="referenceWorkingBeatmap"/>.
2022-01-24 00:49:17 +08:00
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned <see cref="WorkingBeatmap"/>.
/// </summary>
2022-02-15 03:56:05 +08:00
/// <remarks>
/// Contrary to <see cref="CopyExistingDifficulty"/>, this method does not preserve hitobjects and beatmap-level settings from <paramref name="referenceWorkingBeatmap"/>.
/// The created beatmap will have zero hitobjects and will have default settings (including difficulty settings), but will preserve metadata and existing timing points.
/// </remarks>
/// <param name="targetBeatmapSet">The <see cref="BeatmapSetInfo"/> to add the new difficulty to.</param>
/// <param name="referenceWorkingBeatmap">The <see cref="WorkingBeatmap"/> to use as a baseline reference when creating the new difficulty.</param>
/// <param name="rulesetInfo">The ruleset with which the new difficulty should be created.</param>
public virtual WorkingBeatmap CreateNewDifficulty ( BeatmapSetInfo targetBeatmapSet , WorkingBeatmap referenceWorkingBeatmap , RulesetInfo rulesetInfo )
2022-01-24 00:49:17 +08:00
{
2022-02-15 03:56:05 +08:00
var playableBeatmap = referenceWorkingBeatmap . GetPlayableBeatmap ( rulesetInfo ) ;
2022-01-24 00:49:17 +08:00
2022-02-17 06:12:13 +08:00
var newBeatmapInfo = new BeatmapInfo ( rulesetInfo , new BeatmapDifficulty ( ) , playableBeatmap . Metadata . DeepClone ( ) )
{
DifficultyName = NamingUtils . GetNextBestName ( targetBeatmapSet . Beatmaps . Select ( b = > b . DifficultyName ) , "New Difficulty" )
} ;
2022-02-15 03:56:05 +08:00
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo } ;
foreach ( var timingPoint in playableBeatmap . ControlPointInfo . TimingPoints )
newBeatmap . ControlPointInfo . Add ( timingPoint . Time , timingPoint . DeepClone ( ) ) ;
return addDifficultyToSet ( targetBeatmapSet , newBeatmap , referenceWorkingBeatmap . Skin ) ;
}
/// <summary>
/// Add a copy of the provided <paramref name="referenceWorkingBeatmap"/> to the provided <paramref name="targetBeatmapSet"/>.
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned <see cref="WorkingBeatmap"/>.
/// </summary>
/// <remarks>
/// Contrary to <see cref="CreateNewDifficulty"/>, this method creates a nearly-exact copy of <paramref name="referenceWorkingBeatmap"/>
/// (with the exception of a few key properties that cannot be copied under any circumstance, like difficulty name, beatmap hash, or online status).
/// </remarks>
/// <param name="targetBeatmapSet">The <see cref="BeatmapSetInfo"/> to add the copy to.</param>
/// <param name="referenceWorkingBeatmap">The <see cref="WorkingBeatmap"/> to be copied.</param>
public virtual WorkingBeatmap CopyExistingDifficulty ( BeatmapSetInfo targetBeatmapSet , WorkingBeatmap referenceWorkingBeatmap )
{
var newBeatmap = referenceWorkingBeatmap . GetPlayableBeatmap ( referenceWorkingBeatmap . BeatmapInfo . Ruleset ) . Clone ( ) ;
2022-02-07 01:40:51 +08:00
BeatmapInfo newBeatmapInfo ;
2022-02-07 00:52:59 +08:00
2022-02-15 03:56:05 +08:00
newBeatmap . BeatmapInfo = newBeatmapInfo = referenceWorkingBeatmap . BeatmapInfo . Clone ( ) ;
// assign a new ID to the clone.
newBeatmapInfo . ID = Guid . NewGuid ( ) ;
2022-02-17 06:12:13 +08:00
// add "(copy)" suffix to difficulty name, and additionally ensure that it doesn't conflict with any other potentially pre-existing copies.
newBeatmapInfo . DifficultyName = NamingUtils . GetNextBestName (
targetBeatmapSet . Beatmaps . Select ( b = > b . DifficultyName ) ,
$"{newBeatmapInfo.DifficultyName} (copy)" ) ;
2022-02-15 03:56:05 +08:00
// clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps.
newBeatmapInfo . Hash = string . Empty ;
// clear online properties.
newBeatmapInfo . OnlineID = - 1 ;
newBeatmapInfo . Status = BeatmapOnlineStatus . None ;
return addDifficultyToSet ( targetBeatmapSet , newBeatmap , referenceWorkingBeatmap . Skin ) ;
}
2022-01-24 00:49:17 +08:00
2022-02-15 03:56:05 +08:00
private WorkingBeatmap addDifficultyToSet ( BeatmapSetInfo targetBeatmapSet , IBeatmap newBeatmap , ISkin beatmapSkin )
{
2022-02-07 01:40:51 +08:00
// populate circular beatmap set info <-> beatmap info references manually.
2022-06-16 18:01:53 +08:00
// several places like `Save()` or `GetWorkingBeatmap()`
2022-02-07 01:40:51 +08:00
// rely on them being freely traversable in both directions for correct operation.
2022-02-15 03:56:05 +08:00
targetBeatmapSet . Beatmaps . Add ( newBeatmap . BeatmapInfo ) ;
newBeatmap . BeatmapInfo . BeatmapSet = targetBeatmapSet ;
2022-02-07 01:40:51 +08:00
2022-06-16 17:07:04 +08:00
Save ( newBeatmap . BeatmapInfo , newBeatmap , beatmapSkin ) ;
2022-01-24 01:56:19 +08:00
2022-02-07 00:52:59 +08:00
workingBeatmapCache . Invalidate ( targetBeatmapSet ) ;
2022-02-03 18:39:24 +08:00
return GetWorkingBeatmap ( newBeatmap . BeatmapInfo ) ;
2022-01-24 00:49:17 +08:00
}
2021-12-15 13:26:38 +08:00
/// <summary>
/// Delete a beatmap difficulty.
/// </summary>
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
public void Hide ( BeatmapInfo beatmapInfo )
2021-11-05 17:05:31 +08:00
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2021-12-15 13:26:38 +08:00
{
2022-01-25 12:04:05 +08:00
using ( var transaction = r . BeginWrite ( ) )
2022-01-21 00:34:20 +08:00
{
if ( ! beatmapInfo . IsManaged )
2022-01-25 12:04:05 +08:00
beatmapInfo = r . Find < BeatmapInfo > ( beatmapInfo . ID ) ;
2022-01-11 21:55:00 +08:00
2022-01-21 00:34:20 +08:00
beatmapInfo . Hidden = true ;
transaction . Commit ( ) ;
}
} ) ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
/// <summary>
2021-12-15 13:26:38 +08:00
/// Restore a beatmap difficulty.
2021-09-30 15:45:32 +08:00
/// </summary>
2021-12-15 13:26:38 +08:00
/// <param name="beatmapInfo">The beatmap difficulty to restore.</param>
public void Restore ( BeatmapInfo beatmapInfo )
2021-11-05 17:05:31 +08:00
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2021-12-15 13:26:38 +08:00
{
2022-01-25 12:04:05 +08:00
using ( var transaction = r . BeginWrite ( ) )
2022-01-21 00:34:20 +08:00
{
if ( ! beatmapInfo . IsManaged )
2022-01-25 12:04:05 +08:00
beatmapInfo = r . Find < BeatmapInfo > ( beatmapInfo . ID ) ;
2022-01-11 21:55:00 +08:00
2022-01-21 00:34:20 +08:00
beatmapInfo . Hidden = false ;
transaction . Commit ( ) ;
}
} ) ;
2021-11-05 17:05:31 +08:00
}
2021-09-30 15:45:32 +08:00
2022-01-11 22:04:36 +08:00
public void RestoreAll ( )
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2022-01-11 22:04:36 +08:00
{
2022-01-25 12:04:05 +08:00
using ( var transaction = r . BeginWrite ( ) )
2022-01-21 16:08:20 +08:00
{
2022-01-25 12:04:05 +08:00
foreach ( var beatmap in r . All < BeatmapInfo > ( ) . Where ( b = > b . Hidden ) )
2022-01-21 16:08:20 +08:00
beatmap . Hidden = false ;
2022-01-11 22:04:36 +08:00
2022-01-21 16:08:20 +08:00
transaction . Commit ( ) ;
}
} ) ;
2022-01-11 22:04:36 +08:00
}
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>
2021-12-16 19:11:45 +08:00
public List < BeatmapSetInfo > GetAllUsableBeatmapSets ( )
2021-12-15 14:24:26 +08:00
{
2022-06-16 17:56:53 +08:00
return Realm . Run ( r = >
2022-01-21 16:33:26 +08:00
{
2022-01-25 12:04:05 +08:00
r . Refresh ( ) ;
return r . All < BeatmapSetInfo > ( ) . Where ( b = > ! b . DeletePending ) . Detach ( ) ;
2022-01-21 16:33:26 +08:00
} ) ;
2021-12-15 14:24:26 +08:00
}
2021-09-30 15:45:32 +08:00
/// <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>
2022-01-26 12:37:33 +08:00
public Live < BeatmapSetInfo > ? QueryBeatmapSet ( Expression < Func < BeatmapSetInfo , bool > > query )
2021-12-15 14:24:26 +08:00
{
2022-06-16 17:56:53 +08:00
return Realm . Run ( r = > r . All < BeatmapSetInfo > ( ) . FirstOrDefault ( query ) ? . ToLive ( Realm ) ) ;
2021-12-15 14:24:26 +08:00
}
2021-09-30 15:45:32 +08:00
/// <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>
2022-06-16 17:56:53 +08:00
public BeatmapInfo ? QueryBeatmap ( Expression < Func < BeatmapInfo , bool > > query ) = > Realm . Run ( r = > r . All < BeatmapInfo > ( ) . FirstOrDefault ( query ) ? . Detach ( ) ) ;
2021-12-15 14:24:26 +08:00
2021-09-30 15:45:32 +08:00
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
2021-11-15 17:46:11 +08:00
public IWorkingBeatmap DefaultBeatmap = > workingBeatmapCache . DefaultBeatmap ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
/// <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 ) ) ;
2022-06-16 17:56:53 +08:00
Realm . Write ( r = > setInfo . CopyChangesToRealm ( r . Find < BeatmapSetInfo > ( setInfo . ID ) ) ) ;
2022-06-16 17:07:04 +08:00
}
workingBeatmapCache . Invalidate ( beatmapInfo ) ;
static string createBeatmapFilenameFromMetadata ( BeatmapInfo beatmapInfo )
{
var metadata = beatmapInfo . Metadata ;
return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu" . GetValidArchiveContentFilename ( ) ;
}
}
2022-06-06 19:12:18 +08:00
public void DeleteAllVideos ( )
2022-05-18 14:09:58 +08:00
{
2022-06-16 17:56:53 +08:00
Realm . Write ( r = >
2022-05-18 14:09:58 +08:00
{
var items = r . All < BeatmapSetInfo > ( ) . Where ( s = > ! s . DeletePending & & ! s . Protected ) ;
2022-06-16 17:07:04 +08:00
DeleteVideos ( items . ToList ( ) ) ;
2022-05-18 14:09:58 +08:00
} ) ;
}
2022-06-16 17:26:13 +08:00
public void Delete ( Expression < Func < BeatmapSetInfo , bool > > ? filter = null , bool silent = false )
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2022-06-16 17:26:13 +08:00
{
var items = r . All < BeatmapSetInfo > ( ) . Where ( s = > ! s . DeletePending & & ! s . Protected ) ;
if ( filter ! = null )
items = items . Where ( filter ) ;
2022-06-16 17:53:13 +08:00
Delete ( items . ToList ( ) , silent ) ;
2022-06-16 17:26:13 +08:00
} ) ;
}
2022-06-16 17:07:04 +08:00
/// <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 ,
2022-06-16 18:05:25 +08:00
Text = $"Preparing to delete all {HumanisedModelName} videos..." ,
2022-06-16 17:07:04 +08:00
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 + + ;
2022-06-16 18:05:25 +08:00
notification . CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!" ;
2022-06-16 17:07:04 +08:00
}
2022-06-16 18:05:25 +08:00
notification . Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)" ;
2022-06-16 17:07:04 +08:00
notification . Progress = ( float ) + + i / items . Count ;
}
notification . State = ProgressNotificationState . Completed ;
}
2022-01-11 22:04:36 +08:00
public void UndeleteAll ( )
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = > Undelete ( r . All < BeatmapSetInfo > ( ) . Where ( s = > s . DeletePending ) . ToList ( ) ) ) ;
2022-01-11 22:04:36 +08:00
}
2021-09-30 15:45:32 +08:00
#region Implementation of ICanAcceptFiles
2022-06-16 17:07:04 +08:00
public Task Import ( params string [ ] paths ) = > beatmapImporter . Import ( paths ) ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
public Task Import ( params ImportTask [ ] tasks ) = > beatmapImporter . Import ( tasks ) ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
public Task < IEnumerable < Live < BeatmapSetInfo > > > Import ( ProgressNotification notification , params ImportTask [ ] tasks ) = > beatmapImporter . Import ( notification , tasks ) ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
public Task < Live < BeatmapSetInfo > ? > Import ( ImportTask task , bool batchImport = false , CancellationToken cancellationToken = default ) = >
beatmapImporter . Import ( task , batchImport , cancellationToken ) ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
public Task < Live < BeatmapSetInfo > ? > Import ( ArchiveReader archive , bool batchImport = false , CancellationToken cancellationToken = default ) = >
beatmapImporter . Import ( archive , batchImport , cancellationToken ) ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
public Live < BeatmapSetInfo > ? Import ( BeatmapSetInfo item , ArchiveReader ? archive = null , CancellationToken cancellationToken = default ) = >
beatmapImporter . Import ( item , archive , false , cancellationToken ) ;
2021-09-30 15:45:32 +08:00
2022-06-16 17:07:04 +08:00
public IEnumerable < string > HandledExtensions = > beatmapImporter . HandledExtensions ;
2021-09-30 15:45:32 +08:00
#endregion
#region Implementation of IWorkingBeatmapCache
2022-01-18 23:49:16 +08:00
public WorkingBeatmap GetWorkingBeatmap ( BeatmapInfo ? importedBeatmap )
{
// Detached sets don't come with files.
// If we seem to be missing files, now is a good time to re-fetch.
if ( importedBeatmap ? . BeatmapSet ? . Files . Count = = 0 )
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2022-01-18 23:49:16 +08:00
{
2022-01-25 12:04:05 +08:00
var refetch = r . Find < BeatmapInfo > ( importedBeatmap . ID ) ? . Detach ( ) ;
2022-01-18 23:49:16 +08:00
if ( refetch ! = null )
importedBeatmap = refetch ;
2022-01-21 00:34:20 +08:00
} ) ;
2022-01-18 23:49:16 +08:00
}
return workingBeatmapCache . GetWorkingBeatmap ( importedBeatmap ) ;
}
2021-09-30 15:45:32 +08:00
2021-10-06 11:05:30 +08:00
void IWorkingBeatmapCache . Invalidate ( BeatmapSetInfo beatmapSetInfo ) = > workingBeatmapCache . Invalidate ( beatmapSetInfo ) ;
void IWorkingBeatmapCache . Invalidate ( BeatmapInfo beatmapInfo ) = > workingBeatmapCache . Invalidate ( beatmapInfo ) ;
2022-06-16 18:05:25 +08:00
public override bool IsAvailableLocally ( BeatmapSetInfo model ) = > Realm . Run ( realm = > realm . All < BeatmapSetInfo > ( ) . Any ( s = > s . OnlineID = = model . OnlineID ) ) ;
2021-09-30 15:45:32 +08:00
#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 >
2022-01-26 12:37:33 +08:00
public Action < IEnumerable < Live < BeatmapSetInfo > > > ? PostImport
2021-10-15 15:00:09 +08:00
{
2022-06-16 17:07:04 +08:00
set = > beatmapImporter . PostImport = value ;
2021-10-15 15:00:09 +08:00
}
#endregion
2022-06-16 18:05:25 +08:00
public override string HumanisedModelName = > "beatmap" ;
2021-09-30 15:45:32 +08:00
}
2017-07-27 15:56:41 +08:00
}