mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 08:32:57 +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);
|
||||
|
||||
ScoreManager.Performer = this;
|
||||
|
||||
// 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.
|
||||
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@ -61,7 +62,7 @@ namespace osu.Game.Scoring.Legacy
|
||||
workingBeatmap = GetBeatmap(beatmapHash);
|
||||
|
||||
if (workingBeatmap is DummyWorkingBeatmap)
|
||||
throw new BeatmapNotFoundException(beatmapHash);
|
||||
throw new BeatmapNotFoundException(beatmapHash, stream);
|
||||
|
||||
scoreInfo.User = new APIUser { Username = sr.ReadString() };
|
||||
|
||||
@ -349,9 +350,19 @@ namespace osu.Game.Scoring.Legacy
|
||||
{
|
||||
public string Hash { get; }
|
||||
|
||||
public BeatmapNotFoundException(string hash)
|
||||
[CanBeNull]
|
||||
public MemoryStream ScoreStream { get; }
|
||||
|
||||
public BeatmapNotFoundException(string hash, [CanBeNull] Stream scoreStream)
|
||||
{
|
||||
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 osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
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.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Import;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
@ -27,6 +30,8 @@ namespace osu.Game.Scoring
|
||||
{
|
||||
public override IEnumerable<string> HandledExtensions => new[] { ".osr" };
|
||||
|
||||
public IPerformFromScreenRunner? Performer { get; set; }
|
||||
|
||||
protected override string[] HashableFileTypes => new[] { ".osr" };
|
||||
|
||||
private readonly RulesetStore rulesets;
|
||||
@ -54,12 +59,28 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
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);
|
||||
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);
|
||||
|
||||
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.Online.API;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Screens;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
{
|
||||
@ -30,6 +31,12 @@ namespace osu.Game.Scoring
|
||||
private readonly ScoreImporter scoreImporter;
|
||||
private readonly LegacyScoreExporter scoreExporter;
|
||||
|
||||
[CanBeNull]
|
||||
public IPerformFromScreenRunner Performer
|
||||
{
|
||||
set => scoreImporter.Performer = value;
|
||||
}
|
||||
|
||||
public override bool 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