2021-09-30 14:43:49 +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.
using System ;
using System.Collections.Generic ;
2021-12-14 18:47:11 +08:00
using System.Diagnostics ;
2021-09-30 14:43:49 +08:00
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 ;
2021-11-19 15:07:55 +08:00
using osu.Game.Extensions ;
2021-09-30 14:43:49 +08:00
using osu.Game.Skinning ;
2021-12-01 15:19:38 +08:00
using osu.Game.Stores ;
2021-12-14 18:47:11 +08:00
#nullable enable
2021-09-30 14:43:49 +08:00
namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
2021-12-14 18:47:11 +08:00
public class BeatmapModelManager : BeatmapImporter
2021-09-30 14:43:49 +08:00
{
/// <summary>
/// The game working beatmap cache, used to invalidate entries on changes.
/// </summary>
2021-12-14 18:47:11 +08:00
public IWorkingBeatmapCache ? WorkingBeatmapCache { private get ; set ; }
2021-09-30 14:43:49 +08:00
public override IEnumerable < string > HandledExtensions = > new [ ] { ".osz" } ;
protected override string [ ] HashableFileTypes = > new [ ] { ".osu" } ;
2021-12-14 18:47:11 +08:00
public BeatmapModelManager ( RealmContextFactory contextFactory , Storage storage , BeatmapOnlineLookupQueue ? onlineLookupQueue = null )
: base ( contextFactory , storage , onlineLookupQueue )
2021-09-30 14:43:49 +08:00
{
}
protected override bool ShouldDeleteArchive ( string path ) = > Path . GetExtension ( path ) ? . ToLowerInvariant ( ) = = ".osz" ;
/// <summary>
/// Saves an <see cref="IBeatmap"/> file against a given <see cref="BeatmapInfo"/>.
/// </summary>
2021-10-04 15:02:45 +08:00
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
2021-09-30 14:43:49 +08:00
/// <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-12-14 18:47:11 +08:00
public virtual void Save ( BeatmapInfo beatmapInfo , IBeatmap beatmapContent , ISkin ? beatmapSkin = null )
2021-09-30 14:43:49 +08:00
{
2021-10-04 15:02:45 +08:00
var setInfo = beatmapInfo . BeatmapSet ;
2021-09-30 14:43:49 +08:00
2021-12-14 18:47:11 +08:00
Debug . Assert ( setInfo ! = null ) ;
2021-10-13 13:34:31 +08:00
// 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.
2021-11-08 20:23:54 +08:00
// 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 . BaseDifficulty ) ;
2021-10-13 13:34:31 +08:00
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
beatmapContent . BeatmapInfo = beatmapInfo ;
2021-09-30 14:43:49 +08:00
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 ) ;
2021-12-14 18:47:11 +08:00
using ( var realm = ContextFactory . CreateContext ( ) )
using ( var transaction = realm . BeginWrite ( ) )
2021-09-30 14:43:49 +08:00
{
2021-11-24 11:49:57 +08:00
beatmapInfo = setInfo . Beatmaps . Single ( b = > b . Equals ( beatmapInfo ) ) ;
2021-10-07 16:49:13 +08:00
2021-09-30 14:43:49 +08:00
// grab the original file (or create a new one if not found).
2021-12-14 18:47:11 +08:00
var existingFileInfo = setInfo . Files . SingleOrDefault ( f = > string . Equals ( f . Filename , beatmapInfo . Path , StringComparison . OrdinalIgnoreCase ) ) ;
if ( existingFileInfo ! = null )
{
DeleteFile ( setInfo , existingFileInfo ) ;
}
2021-09-30 14:43:49 +08:00
// metadata may have changed; update the path with the standard format.
2021-12-14 18:47:11 +08:00
var metadata = beatmapInfo . Metadata ;
string filename = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu" . GetValidArchiveContentFilename ( ) ;
2021-11-03 15:46:05 +08:00
2021-09-30 14:43:49 +08:00
beatmapInfo . MD5Hash = stream . ComputeMD5Hash ( ) ;
stream . Seek ( 0 , SeekOrigin . Begin ) ;
2021-12-14 18:47:11 +08:00
AddFile ( setInfo , stream , filename , realm ) ;
transaction . Commit ( ) ;
2021-09-30 14:43:49 +08:00
}
}
2021-10-04 15:02:45 +08:00
WorkingBeatmapCache ? . Invalidate ( beatmapInfo ) ;
2021-09-30 14:43:49 +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>
2021-12-14 18:47:11 +08:00
public BeatmapInfo ? QueryBeatmap ( Expression < Func < BeatmapInfo , bool > > query )
{
using ( var context = ContextFactory . CreateContext ( ) )
2022-01-07 13:17:22 +08:00
return context . All < BeatmapInfo > ( ) . FirstOrDefault ( query ) ? . Detach ( ) ;
2021-12-14 18:47:11 +08:00
}
2021-09-30 14:43:49 +08:00
}
}