mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 14:17:26 +08:00
fetch missing beatmap when import score with missing beatmap
This commit is contained in:
parent
dc9c68b642
commit
eef63b41da
@ -0,0 +1,96 @@
|
|||||||
|
// 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.Net;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Screens.Import;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public partial class TestSceneReplayMissingBeatmap : OsuGameTestScene
|
||||||
|
{
|
||||||
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSceneMissingBeatmapWithOnlineAvailable()
|
||||||
|
{
|
||||||
|
var beatmap = new APIBeatmap
|
||||||
|
{
|
||||||
|
OnlineBeatmapSetID = 173612
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapset = new APIBeatmapSet
|
||||||
|
{
|
||||||
|
OnlineID = 173612,
|
||||||
|
};
|
||||||
|
|
||||||
|
setupBeatmapResponse(beatmap, beatmapset);
|
||||||
|
|
||||||
|
AddStep("import score", () =>
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||||
|
{
|
||||||
|
var importTask = new ImportTask(resourceStream, "replay.osr");
|
||||||
|
|
||||||
|
Game.ScoreManager.Import(new[] { importTask });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Replay missing screen show", () => Game.ScreenStack.CurrentScreen.GetType() == typeof(ReplayMissingBeatmapScreen));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSceneMissingBeatmapWithOnlineUnavailable()
|
||||||
|
{
|
||||||
|
setupFailedResponse();
|
||||||
|
|
||||||
|
AddStep("import score", () =>
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||||
|
{
|
||||||
|
var importTask = new ImportTask(resourceStream, "replay.osr");
|
||||||
|
|
||||||
|
Game.ScoreManager.Import(new[] { importTask });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Replay missing screen not show", () => Game.ScreenStack.CurrentScreen.GetType() != typeof(ReplayMissingBeatmapScreen));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupBeatmapResponse(APIBeatmap b, APIBeatmapSet s)
|
||||||
|
=> AddStep("setup response", () =>
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
if (request is GetBeatmapRequest getBeatmapRequest)
|
||||||
|
{
|
||||||
|
getBeatmapRequest.TriggerSuccess(b);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request is GetBeatmapSetRequest getBeatmapSetRequest)
|
||||||
|
{
|
||||||
|
getBeatmapSetRequest.TriggerSuccess(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
private void setupFailedResponse()
|
||||||
|
=> AddStep("setup failed response", () =>
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
request.TriggerFailure(new WebException());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -854,6 +854,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
MultiplayerClient.PostNotification = n => Notifications.Post(n);
|
MultiplayerClient.PostNotification = n => Notifications.Post(n);
|
||||||
|
|
||||||
|
ScoreManager.Performer = this;
|
||||||
|
|
||||||
// make config aware of how to lookup skins for on-screen display purposes.
|
// make config aware of how to lookup skins for on-screen display purposes.
|
||||||
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
|
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
|
||||||
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";
|
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";
|
||||||
|
@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
@ -61,7 +62,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
workingBeatmap = GetBeatmap(beatmapHash);
|
workingBeatmap = GetBeatmap(beatmapHash);
|
||||||
|
|
||||||
if (workingBeatmap is DummyWorkingBeatmap)
|
if (workingBeatmap is DummyWorkingBeatmap)
|
||||||
throw new BeatmapNotFoundException(beatmapHash);
|
throw new BeatmapNotFoundException(beatmapHash, stream);
|
||||||
|
|
||||||
scoreInfo.User = new APIUser { Username = sr.ReadString() };
|
scoreInfo.User = new APIUser { Username = sr.ReadString() };
|
||||||
|
|
||||||
@ -349,9 +350,19 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
{
|
{
|
||||||
public string Hash { get; }
|
public string Hash { get; }
|
||||||
|
|
||||||
public BeatmapNotFoundException(string hash)
|
[CanBeNull]
|
||||||
|
public MemoryStream ScoreStream { get; }
|
||||||
|
|
||||||
|
public BeatmapNotFoundException(string hash, [CanBeNull] Stream scoreStream)
|
||||||
{
|
{
|
||||||
Hash = hash;
|
Hash = hash;
|
||||||
|
|
||||||
|
if (scoreStream != null)
|
||||||
|
{
|
||||||
|
ScoreStream = new MemoryStream();
|
||||||
|
scoreStream.Position = 0;
|
||||||
|
scoreStream.CopyTo(ScoreStream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
@ -19,6 +20,8 @@ using osu.Game.Online.API.Requests;
|
|||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Import;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -27,6 +30,8 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
public override IEnumerable<string> HandledExtensions => new[] { ".osr" };
|
public override IEnumerable<string> HandledExtensions => new[] { ".osr" };
|
||||||
|
|
||||||
|
public IPerformFromScreenRunner? Performer { get; set; }
|
||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osr" };
|
protected override string[] HashableFileTypes => new[] { ".osr" };
|
||||||
|
|
||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
@ -54,12 +59,28 @@ namespace osu.Game.Scoring
|
|||||||
}
|
}
|
||||||
catch (LegacyScoreDecoder.BeatmapNotFoundException e)
|
catch (LegacyScoreDecoder.BeatmapNotFoundException e)
|
||||||
{
|
{
|
||||||
|
onMissingBeatmap(e);
|
||||||
Logger.Log($@"Score '{name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database);
|
Logger.Log($@"Score '{name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e)
|
||||||
|
{
|
||||||
|
var req = new GetBeatmapRequest(new BeatmapInfo
|
||||||
|
{
|
||||||
|
MD5Hash = e.Hash
|
||||||
|
});
|
||||||
|
|
||||||
|
req.Success += res =>
|
||||||
|
{
|
||||||
|
Performer?.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, e.ScoreStream)));
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Queue(req);
|
||||||
|
}
|
||||||
|
|
||||||
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
|
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
|
||||||
|
|
||||||
protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
|
protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
@ -30,6 +31,12 @@ namespace osu.Game.Scoring
|
|||||||
private readonly ScoreImporter scoreImporter;
|
private readonly ScoreImporter scoreImporter;
|
||||||
private readonly LegacyScoreExporter scoreExporter;
|
private readonly LegacyScoreExporter scoreExporter;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public IPerformFromScreenRunner Performer
|
||||||
|
{
|
||||||
|
set => scoreImporter.Performer = value;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool PauseImports
|
public override bool PauseImports
|
||||||
{
|
{
|
||||||
get => base.PauseImports;
|
get => base.PauseImports;
|
||||||
|
210
osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs
Normal file
210
osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
// 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;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osuTK;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Import
|
||||||
|
{
|
||||||
|
[Cached(typeof(IPreviewTrackOwner))]
|
||||||
|
public partial class ReplayMissingBeatmapScreen : OsuScreen, IPreviewTrackOwner
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapModelDownloader beatmapDownloader { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scoreManager { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RealmAccess realm { get; set; } = null!;
|
||||||
|
|
||||||
|
private IDisposable? realmSubscription;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private INotificationOverlay? notificationOverlay { get; set; }
|
||||||
|
|
||||||
|
private Container beatmapPanelContainer = null!;
|
||||||
|
private ReplayDownloadButton replayDownloadButton = null!;
|
||||||
|
private SettingsCheckbox automaticDownload = null!;
|
||||||
|
|
||||||
|
private readonly MemoryStream? scoreStream;
|
||||||
|
private readonly APIBeatmap beatmap;
|
||||||
|
|
||||||
|
private APIBeatmapSet? beatmapSetInfo;
|
||||||
|
|
||||||
|
public ReplayMissingBeatmapScreen(APIBeatmap beatmap, MemoryStream? scoreStream = null)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
this.scoreStream = scoreStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours, OsuConfigManager config)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 20,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
AutoSizeDuration = 500,
|
||||||
|
AutoSizeEasing = Easing.OutQuint,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding(20),
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Spacing = new Vector2(15),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Beatmap info",
|
||||||
|
Font = OsuFont.Default.With(size: 30),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Spacing = new Vector2(15),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
beatmapPanelContainer = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
automaticDownload = new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "Automatically download beatmaps",
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.AutomaticallyDownloadWhenSpectating),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
replayDownloadButton = new ReplayDownloadButton(new ScoreInfo())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
var onlineBeatmapRequest = new GetBeatmapSetRequest(beatmap.OnlineBeatmapSetID);
|
||||||
|
|
||||||
|
onlineBeatmapRequest.Success += res =>
|
||||||
|
{
|
||||||
|
beatmapSetInfo = res;
|
||||||
|
beatmapPanelContainer.Child = new BeatmapCardNormal(res, allowExpansion: false);
|
||||||
|
checkForAutomaticDownload(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Queue(onlineBeatmapRequest);
|
||||||
|
|
||||||
|
realmSubscription = realm.RegisterForNotifications(
|
||||||
|
realm => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForAutomaticDownload(APIBeatmapSet beatmap)
|
||||||
|
{
|
||||||
|
if (!automaticDownload.Current.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmapDownloader.Download(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
|
||||||
|
{
|
||||||
|
if (changes?.InsertedIndices == null) return;
|
||||||
|
|
||||||
|
if (beatmapSetInfo == null) return;
|
||||||
|
|
||||||
|
if (scoreStream == null) return;
|
||||||
|
|
||||||
|
if (sender.Any(b => b.OnlineID == beatmapSetInfo.OnlineID))
|
||||||
|
{
|
||||||
|
var progressNotification = new ImportProgressNotification();
|
||||||
|
var importTask = new ImportTask(scoreStream, "score.osr");
|
||||||
|
|
||||||
|
scoreManager.Import(progressNotification, new[] { importTask })
|
||||||
|
.ContinueWith(s =>
|
||||||
|
{
|
||||||
|
s.GetResultSafely<IEnumerable<Live<ScoreInfo>>>().FirstOrDefault()?.PerformRead(score =>
|
||||||
|
{
|
||||||
|
Guid scoreid = score.ID;
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
replayDownloadButton.Score.Value = realm.Realm.Find<ScoreInfo>(scoreid) ?? new ScoreInfo();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationOverlay?.Post(progressNotification);
|
||||||
|
|
||||||
|
realmSubscription?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
realmSubscription?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user