2019-01-24 08:43:03 +00: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-11-28 07:47:10 +00:00
2018-11-28 08:19:58 +00:00
using System ;
using System.Collections.Generic ;
2024-01-17 18:00:21 +00:00
using System.Diagnostics ;
2018-11-28 08:19:58 +00:00
using System.Linq ;
using System.Linq.Expressions ;
2020-09-09 08:37:11 +00:00
using System.Threading ;
2021-06-27 04:06:20 +00:00
using System.Threading.Tasks ;
2020-08-28 12:34:34 +00:00
using osu.Framework.Bindables ;
2024-04-14 23:22:58 +00:00
using osu.Framework.Logging ;
2018-11-28 07:47:10 +00:00
using osu.Framework.Platform ;
using osu.Game.Beatmaps ;
2020-08-28 12:34:34 +00:00
using osu.Game.Configuration ;
2018-11-28 07:47:10 +00:00
using osu.Game.Database ;
using osu.Game.IO.Archives ;
2024-07-01 03:07:13 +00:00
using osu.Game.Online.API ;
2021-09-30 09:21:16 +00:00
using osu.Game.Overlays.Notifications ;
2018-11-28 07:47:10 +00:00
using osu.Game.Rulesets ;
2020-08-28 10:16:46 +00:00
using osu.Game.Rulesets.Scoring ;
2023-05-18 09:53:43 +00:00
using osu.Game.Scoring.Legacy ;
2018-11-28 07:47:10 +00:00
namespace osu.Game.Scoring
{
2022-06-16 09:53:13 +00:00
public class ScoreManager : ModelManager < ScoreInfo > , IModelImporter < ScoreInfo >
2018-11-28 07:47:10 +00:00
{
2024-01-17 18:00:21 +00:00
private readonly Func < BeatmapManager > beatmaps ;
2024-04-14 22:53:29 +00:00
private readonly OsuConfigManager ? configManager ;
2022-06-16 09:11:50 +00:00
private readonly ScoreImporter scoreImporter ;
2022-12-11 09:30:24 +00:00
private readonly LegacyScoreExporter scoreExporter ;
2020-08-28 12:34:34 +00:00
2023-01-09 16:10:20 +00:00
public override bool PauseImports
{
get = > base . PauseImports ;
set
{
base . PauseImports = value ;
scoreImporter . PauseImports = value ;
}
}
2022-09-08 13:06:44 +00:00
public ScoreManager ( RulesetStore rulesets , Func < BeatmapManager > beatmaps , Storage storage , RealmAccess realm , IAPIProvider api ,
2024-04-14 22:53:29 +00:00
OsuConfigManager ? configManager = null )
2022-06-16 09:53:13 +00:00
: base ( storage , realm )
2018-11-28 07:47:10 +00:00
{
2024-01-17 18:00:21 +00:00
this . beatmaps = beatmaps ;
2020-08-28 12:34:34 +00:00
this . configManager = configManager ;
2018-11-28 08:19:58 +00:00
2022-09-09 04:57:01 +00:00
scoreImporter = new ScoreImporter ( rulesets , beatmaps , storage , realm , api )
2022-06-16 10:48:18 +00:00
{
PostNotification = obj = > PostNotification ? . Invoke ( obj )
} ;
2022-12-11 09:30:24 +00:00
2023-04-09 13:15:00 +00:00
scoreExporter = new LegacyScoreExporter ( storage )
2022-12-11 09:30:24 +00:00
{
PostNotification = obj = > PostNotification ? . Invoke ( obj )
} ;
2018-11-28 07:47:10 +00:00
}
2018-11-28 08:19:58 +00:00
2024-04-18 04:18:54 +00:00
/// <summary>
/// Retrieve a <see cref="Score"/> from a given <see cref="IScoreInfo"/>.
/// </summary>
/// <param name="scoreInfo">The <see cref="IScoreInfo"/> to convert.</param>
2024-04-22 18:17:27 +00:00
/// <returns>The <see cref="Score"/>. Null if the score cannot be found in the database.</returns>
2024-04-18 04:18:54 +00:00
/// <remarks>
/// The <see cref="IScoreInfo"/> is re-retrieved from the database to ensure all the required data
/// for retrieving a replay are present (may have missing properties if it was retrieved from online data).
/// </remarks>
2024-04-16 03:48:51 +00:00
public Score ? GetScore ( IScoreInfo scoreInfo )
{
ScoreInfo ? databasedScoreInfo = getDatabasedScoreInfo ( scoreInfo ) ;
return databasedScoreInfo = = null ? null : scoreImporter . GetScore ( databasedScoreInfo ) ;
}
2021-06-27 04:06:20 +00:00
2021-12-13 10:01:20 +00:00
/// <summary>
/// Perform a lookup query on available <see cref="ScoreInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
2024-04-14 22:53:29 +00:00
public ScoreInfo ? Query ( Expression < Func < ScoreInfo , bool > > query )
2021-12-13 10:01:20 +00:00
{
2022-06-16 09:56:53 +00:00
return Realm . Run ( r = > r . All < ScoreInfo > ( ) . FirstOrDefault ( query ) ? . Detach ( ) ) ;
2021-12-13 10:01:20 +00:00
}
2020-08-28 10:16:46 +00:00
2024-04-16 03:48:51 +00:00
private ScoreInfo ? getDatabasedScoreInfo ( IScoreInfo originalScoreInfo )
2024-04-14 23:22:58 +00:00
{
ScoreInfo ? databasedScoreInfo = null ;
2024-05-20 12:48:36 +00:00
if ( originalScoreInfo is ScoreInfo scoreInfo & & ! string . IsNullOrEmpty ( scoreInfo . Hash ) )
2024-05-14 09:14:46 +00:00
databasedScoreInfo = Query ( s = > s . Hash = = scoreInfo . Hash ) ;
2024-04-14 23:22:58 +00:00
if ( originalScoreInfo . OnlineID > 0 )
2024-05-14 09:14:46 +00:00
databasedScoreInfo ? ? = Query ( s = > s . OnlineID = = originalScoreInfo . OnlineID ) ;
2024-04-14 23:22:58 +00:00
if ( originalScoreInfo . LegacyOnlineID > 0 )
databasedScoreInfo ? ? = Query ( s = > s . LegacyOnlineID = = originalScoreInfo . LegacyOnlineID ) ;
if ( databasedScoreInfo = = null )
{
Logger . Log ( "The requested score could not be found locally." , LoggingTarget . Information ) ;
return null ;
}
return databasedScoreInfo ;
}
2020-09-09 08:04:02 +00:00
/// <summary>
/// Retrieves a bindable that represents the total score of a <see cref="ScoreInfo"/>.
/// </summary>
/// <remarks>
/// Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </remarks>
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
/// <returns>The bindable containing the total score.</returns>
2024-04-14 23:22:58 +00:00
public Bindable < long > GetBindableTotalScore ( ScoreInfo score ) = > new TotalScoreBindable ( score , configManager ) ;
2020-08-28 10:16:46 +00:00
2020-09-09 08:04:02 +00:00
/// <summary>
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
/// </summary>
/// <remarks>
/// Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </remarks>
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
/// <returns>The bindable containing the formatted total score string.</returns>
2024-04-14 23:22:58 +00:00
public Bindable < string > GetBindableTotalScoreString ( ScoreInfo score ) = > new TotalScoreStringBindable ( GetBindableTotalScore ( score ) ) ;
2020-09-09 08:04:02 +00:00
/// <summary>
/// Provides the total score of a <see cref="ScoreInfo"/>. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </summary>
2020-08-28 13:51:19 +00:00
private class TotalScoreBindable : Bindable < long >
2020-08-28 12:34:34 +00:00
{
2022-03-30 04:15:41 +00:00
private readonly Bindable < ScoringMode > scoringMode = new Bindable < ScoringMode > ( ) ;
2021-09-01 11:56:23 +00:00
2020-09-09 08:04:02 +00:00
/// <summary>
/// Creates a new <see cref="TotalScoreBindable"/>.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
2022-03-30 04:15:41 +00:00
/// <param name="configManager">The config.</param>
2024-04-14 22:53:29 +00:00
public TotalScoreBindable ( ScoreInfo score , OsuConfigManager ? configManager )
2020-08-28 10:16:46 +00:00
{
2022-03-30 04:15:41 +00:00
configManager ? . BindWith ( OsuSetting . ScoreDisplayMode , scoringMode ) ;
2023-05-18 09:55:10 +00:00
scoringMode . BindValueChanged ( mode = > Value = score . GetDisplayScore ( mode . NewValue ) , true ) ;
2020-08-28 13:51:19 +00:00
}
}
2020-09-09 08:04:02 +00:00
/// <summary>
/// Provides the total score of a <see cref="ScoreInfo"/> as a formatted string. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </summary>
2020-08-28 13:51:19 +00:00
private class TotalScoreStringBindable : Bindable < string >
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference)
private readonly IBindable < long > totalScore ;
public TotalScoreStringBindable ( IBindable < long > totalScore )
{
this . totalScore = totalScore ;
this . totalScore . BindValueChanged ( v = > Value = v . NewValue . ToString ( "N0" ) , true ) ;
2020-08-28 12:34:34 +00:00
}
2020-08-28 10:16:46 +00:00
}
2021-09-30 09:21:16 +00:00
2024-04-14 22:53:29 +00:00
public void Delete ( Expression < Func < ScoreInfo , bool > > ? filter = null , bool silent = false )
2021-12-13 10:01:20 +00:00
{
2022-06-16 09:56:53 +00:00
Realm . Run ( r = >
2021-12-13 10:01:20 +00:00
{
2022-01-25 04:04:05 +00:00
var items = r . All < ScoreInfo > ( )
. Where ( s = > ! s . DeletePending ) ;
2021-12-13 10:01:20 +00:00
if ( filter ! = null )
items = items . Where ( filter ) ;
2022-06-16 09:53:13 +00:00
Delete ( items . ToList ( ) , silent ) ;
2022-01-21 08:08:20 +00:00
} ) ;
2021-12-13 10:01:20 +00:00
}
2022-01-28 06:08:39 +00:00
public void Delete ( BeatmapInfo beatmap , bool silent = false )
{
2022-06-16 09:56:53 +00:00
Realm . Run ( r = >
2022-01-28 06:08:39 +00:00
{
2023-07-06 20:08:48 +00:00
var beatmapScores = r . Find < BeatmapInfo > ( beatmap . ID ) ! . Scores . ToList ( ) ;
2022-06-16 09:53:13 +00:00
Delete ( beatmapScores , silent ) ;
2022-01-28 06:08:39 +00:00
} ) ;
}
2022-06-16 09:11:50 +00:00
public Task Import ( params string [ ] paths ) = > scoreImporter . Import ( paths ) ;
2021-09-30 09:21:16 +00:00
2022-12-13 12:03:25 +00:00
public Task Import ( ImportTask [ ] imports , ImportParameters parameters = default ) = > scoreImporter . Import ( imports , parameters ) ;
2021-09-30 09:21:16 +00:00
2023-09-04 10:21:23 +00:00
public override bool IsAvailableLocally ( ScoreInfo model )
= > Realm . Run ( realm = > realm . All < ScoreInfo > ( )
// this basically inlines `ModelExtension.MatchesOnlineID(IScoreInfo, IScoreInfo)`,
// because that method can't be used here, as realm can't translate it to its query language.
. Any ( s = > s . OnlineID = = model . OnlineID | | s . LegacyOnlineID = = model . LegacyOnlineID ) ) ;
2022-06-16 10:05:25 +00:00
2022-06-16 09:11:50 +00:00
public IEnumerable < string > HandledExtensions = > scoreImporter . HandledExtensions ;
2021-09-30 09:21:16 +00:00
2022-12-13 12:03:25 +00:00
public Task < IEnumerable < Live < ScoreInfo > > > Import ( ProgressNotification notification , ImportTask [ ] tasks , ImportParameters parameters = default ) = > scoreImporter . Import ( notification , tasks ) ;
2021-09-30 09:21:16 +00:00
2024-04-18 04:18:54 +00:00
/// <summary>
/// Export a replay from a given <see cref="IScoreInfo"/>.
/// </summary>
/// <param name="scoreInfo">The <see cref="IScoreInfo"/> to export.</param>
2024-04-22 18:22:39 +00:00
/// <returns>The <see cref="Task"/>. Return <see cref="Task.CompletedTask"/> if the score cannot be found in the database.</returns>
2024-04-18 04:18:54 +00:00
/// <remarks>
/// The <see cref="IScoreInfo"/> is re-retrieved from the database to ensure all the required data
/// for exporting a replay are present (may have missing properties if it was retrieved from online data).
/// </remarks>
2024-04-22 18:22:39 +00:00
public Task Export ( ScoreInfo scoreInfo )
2024-04-16 03:48:51 +00:00
{
ScoreInfo ? databasedScoreInfo = getDatabasedScoreInfo ( scoreInfo ) ;
2024-04-22 18:22:39 +00:00
return databasedScoreInfo = = null ? Task . CompletedTask : scoreExporter . ExportAsync ( databasedScoreInfo . ToLive ( Realm ) ) ;
2024-04-16 03:48:51 +00:00
}
2022-12-11 09:30:24 +00:00
2024-04-14 22:53:29 +00:00
public Task < Live < ScoreInfo > ? > ImportAsUpdate ( ProgressNotification notification , ImportTask task , ScoreInfo original ) = > scoreImporter . ImportAsUpdate ( notification , task , original ) ;
2024-07-01 03:07:13 +00:00
public Task < ExternalEditOperation < ScoreInfo > > BeginExternalEditing ( ScoreInfo model ) = > scoreImporter . BeginExternalEditing ( model ) ;
2022-07-26 06:46:29 +00:00
2024-04-14 22:53:29 +00:00
public Live < ScoreInfo > ? Import ( ScoreInfo item , ArchiveReader ? archive = null , ImportParameters parameters = default , CancellationToken cancellationToken = default ) = >
2022-12-12 14:56:11 +00:00
scoreImporter . ImportModel ( item , archive , parameters , cancellationToken ) ;
2021-09-30 09:21:16 +00:00
2022-09-08 13:06:44 +00:00
/// <summary>
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
/// </summary>
/// <param name="score">The score to populate the statistics of.</param>
2024-01-17 18:00:21 +00:00
public void PopulateMaximumStatistics ( ScoreInfo score )
{
Debug . Assert ( score . BeatmapInfo ! = null ) ;
LegacyScoreDecoder . PopulateMaximumStatistics ( score , beatmaps ( ) . GetWorkingBeatmap ( score . BeatmapInfo . Detach ( ) ) ) ;
}
2022-09-08 13:06:44 +00:00
2021-09-30 09:21:16 +00:00
#region Implementation of IPresentImports < ScoreInfo >
2024-04-14 22:53:29 +00:00
public Action < IEnumerable < Live < ScoreInfo > > > ? PresentImport
2021-09-30 09:21:16 +00:00
{
2022-06-20 09:21:37 +00:00
set = > scoreImporter . PresentImport = value ;
2021-09-30 09:21:16 +00:00
}
#endregion
2018-11-28 07:47:10 +00:00
}
}