mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 00:42:55 +08:00
Merge pull request #11515 from frenzibyte/multiplayer-beatmap-tracker
Add beatmap availability tracker component for multiplayer and playlists
This commit is contained in:
commit
56f9dae4f6
@ -0,0 +1,188 @@
|
|||||||
|
// 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Online
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneOnlinePlayBeatmapAvailabilityTracker : OsuTestScene
|
||||||
|
{
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
private TestBeatmapManager beatmaps;
|
||||||
|
|
||||||
|
private string testBeatmapFile;
|
||||||
|
private BeatmapInfo testBeatmapInfo;
|
||||||
|
private BeatmapSetInfo testBeatmapSet;
|
||||||
|
|
||||||
|
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||||
|
private OnlinePlayBeatmapAvailablilityTracker availablilityTracker;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio, GameHost host)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
|
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, host, Beatmap.Default));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
beatmaps.AllowImport = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
testBeatmapFile = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
||||||
|
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
|
var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID);
|
||||||
|
if (existing != null)
|
||||||
|
beatmaps.Delete(existing);
|
||||||
|
|
||||||
|
selectedItem.Value = new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = testBeatmapInfo },
|
||||||
|
Ruleset = { Value = testBeatmapInfo.Ruleset },
|
||||||
|
};
|
||||||
|
|
||||||
|
Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||||
|
{
|
||||||
|
SelectedItem = { BindTarget = selectedItem, }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapDownloadingFlow()
|
||||||
|
{
|
||||||
|
AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet));
|
||||||
|
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
|
||||||
|
|
||||||
|
AddStep("start downloading", () => beatmaps.Download(testBeatmapSet));
|
||||||
|
addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f));
|
||||||
|
|
||||||
|
AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f));
|
||||||
|
addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f));
|
||||||
|
|
||||||
|
AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile));
|
||||||
|
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
||||||
|
|
||||||
|
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
||||||
|
AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true);
|
||||||
|
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTrackerRespectsSoftDeleting()
|
||||||
|
{
|
||||||
|
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
||||||
|
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
|
||||||
|
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
|
||||||
|
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID)));
|
||||||
|
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
|
||||||
|
|
||||||
|
AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID)));
|
||||||
|
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTrackerRespectsChecksum()
|
||||||
|
{
|
||||||
|
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
||||||
|
|
||||||
|
AddStep("import altered beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
|
||||||
|
});
|
||||||
|
addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded);
|
||||||
|
|
||||||
|
AddStep("recreate tracker", () => Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||||
|
{
|
||||||
|
SelectedItem = { BindTarget = selectedItem }
|
||||||
|
});
|
||||||
|
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)
|
||||||
|
{
|
||||||
|
AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BeatmapInfo getTestBeatmapInfo(string archiveFile)
|
||||||
|
{
|
||||||
|
BeatmapInfo info;
|
||||||
|
|
||||||
|
using (var archive = new ZipArchiveReader(File.OpenRead(archiveFile)))
|
||||||
|
using (var stream = archive.GetStream("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var reader = new LineBufferedReader(stream))
|
||||||
|
{
|
||||||
|
var decoder = Decoder.GetDecoder<Beatmap>(reader);
|
||||||
|
var beatmap = decoder.Decode(reader);
|
||||||
|
|
||||||
|
info = beatmap.BeatmapInfo;
|
||||||
|
info.BeatmapSet.Beatmaps = new List<BeatmapInfo> { info };
|
||||||
|
info.BeatmapSet.Metadata = info.Metadata;
|
||||||
|
info.MD5Hash = stream.ComputeMD5Hash();
|
||||||
|
info.Hash = stream.ComputeSHA2Hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBeatmapManager : BeatmapManager
|
||||||
|
{
|
||||||
|
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
public Task<BeatmapSetInfo> CurrentImportTask { get; private set; }
|
||||||
|
|
||||||
|
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize)
|
||||||
|
=> new TestDownloadRequest(set);
|
||||||
|
|
||||||
|
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
||||||
|
: base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, performOnlineLookups)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await AllowImport.Task;
|
||||||
|
return await (CurrentImportTask = base.Import(item, archive, cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDownloadRequest : ArchiveDownloadRequest<BeatmapSetInfo>
|
||||||
|
{
|
||||||
|
public new void SetProgress(float progress) => base.SetProgress(progress);
|
||||||
|
public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename);
|
||||||
|
|
||||||
|
public TestDownloadRequest(BeatmapSetInfo model)
|
||||||
|
: base(model)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -308,7 +308,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="item">The model to be imported.</param>
|
/// <param name="item">The model to be imported.</param>
|
||||||
/// <param name="archive">An optional archive to use for model population.</param>
|
/// <param name="archive">An optional archive to use for model population.</param>
|
||||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||||
public async Task<TModel> Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
|
public virtual async Task<TModel> Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
|
|
||||||
@ -28,13 +29,19 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total));
|
private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total));
|
||||||
|
|
||||||
protected APIDownloadRequest()
|
protected void TriggerSuccess(string filename)
|
||||||
{
|
{
|
||||||
base.Success += onSuccess;
|
if (this.filename != null)
|
||||||
|
throw new InvalidOperationException("Attempted to trigger success more than once");
|
||||||
|
|
||||||
|
this.filename = filename;
|
||||||
|
|
||||||
|
TriggerSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSuccess()
|
internal override void TriggerSuccess()
|
||||||
{
|
{
|
||||||
|
base.TriggerSuccess();
|
||||||
Success?.Invoke(filename);
|
Success?.Invoke(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
public readonly TModel Model;
|
public readonly TModel Model;
|
||||||
|
|
||||||
public float Progress;
|
public float Progress { get; private set; }
|
||||||
|
|
||||||
public event Action<float> DownloadProgressed;
|
public event Action<float> DownloadProgressed;
|
||||||
|
|
||||||
@ -18,7 +18,13 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
Model = model;
|
Model = model;
|
||||||
|
|
||||||
Progressed += (current, total) => DownloadProgressed?.Invoke(Progress = (float)current / total);
|
Progressed += (current, total) => SetProgress((float)current / total);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetProgress(float progress)
|
||||||
|
{
|
||||||
|
Progress = progress;
|
||||||
|
DownloadProgressed?.Invoke(progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Online
|
|||||||
protected readonly Bindable<TModel> Model = new Bindable<TModel>();
|
protected readonly Bindable<TModel> Model = new Bindable<TModel>();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private TModelManager manager { get; set; }
|
protected TModelManager Manager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the current download state of the <typeparamref name="TModel"/>, whether is has already been downloaded, is in progress, or is not downloaded.
|
/// Holds the current download state of the <typeparamref name="TModel"/>, whether is has already been downloaded, is in progress, or is not downloaded.
|
||||||
@ -46,25 +47,41 @@ namespace osu.Game.Online
|
|||||||
{
|
{
|
||||||
if (modelInfo.NewValue == null)
|
if (modelInfo.NewValue == null)
|
||||||
attachDownload(null);
|
attachDownload(null);
|
||||||
else if (manager?.IsAvailableLocally(modelInfo.NewValue) == true)
|
else if (IsModelAvailableLocally())
|
||||||
State.Value = DownloadState.LocallyAvailable;
|
State.Value = DownloadState.LocallyAvailable;
|
||||||
else
|
else
|
||||||
attachDownload(manager?.GetExistingDownload(modelInfo.NewValue));
|
attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue));
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
if (manager == null)
|
if (Manager == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
managerDownloadBegan = manager.DownloadBegan.GetBoundCopy();
|
managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy();
|
||||||
managerDownloadBegan.BindValueChanged(downloadBegan);
|
managerDownloadBegan.BindValueChanged(downloadBegan);
|
||||||
managerDownloadFailed = manager.DownloadFailed.GetBoundCopy();
|
managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy();
|
||||||
managerDownloadFailed.BindValueChanged(downloadFailed);
|
managerDownloadFailed.BindValueChanged(downloadFailed);
|
||||||
managedUpdated = manager.ItemUpdated.GetBoundCopy();
|
managedUpdated = Manager.ItemUpdated.GetBoundCopy();
|
||||||
managedUpdated.BindValueChanged(itemUpdated);
|
managedUpdated.BindValueChanged(itemUpdated);
|
||||||
managerRemoved = manager.ItemRemoved.GetBoundCopy();
|
managerRemoved = Manager.ItemRemoved.GetBoundCopy();
|
||||||
managerRemoved.BindValueChanged(itemRemoved);
|
managerRemoved.BindValueChanged(itemRemoved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks that a database model matches the one expected to be downloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// For online play, this could be used to check that the databased model matches the online beatmap.
|
||||||
|
/// </example>
|
||||||
|
/// <param name="databasedModel">The model in database.</param>
|
||||||
|
protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the given model is available in the database.
|
||||||
|
/// By default, this calls <see cref="IModelDownloader{TModel}.IsAvailableLocally"/>,
|
||||||
|
/// but can be overriden to add additional checks for verifying the model in database.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool IsModelAvailableLocally() => Manager?.IsAvailableLocally(Model.Value) == true;
|
||||||
|
|
||||||
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<TModel>>> weakRequest)
|
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<TModel>>> weakRequest)
|
||||||
{
|
{
|
||||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||||
@ -134,23 +151,35 @@ namespace osu.Game.Online
|
|||||||
private void itemUpdated(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
private void itemUpdated(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
||||||
{
|
{
|
||||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||||
setDownloadStateFromManager(item, DownloadState.LocallyAvailable);
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!item.Equals(Model.Value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!VerifyDatabasedModel(item))
|
||||||
|
{
|
||||||
|
State.Value = DownloadState.NotDownloaded;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
State.Value = DownloadState.LocallyAvailable;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void itemRemoved(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
private void itemRemoved(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
||||||
{
|
{
|
||||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||||
setDownloadStateFromManager(item, DownloadState.NotDownloaded);
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (item.Equals(Model.Value))
|
||||||
|
State.Value = DownloadState.NotDownloaded;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() =>
|
|
||||||
{
|
|
||||||
if (!s.Equals(Model.Value))
|
|
||||||
return;
|
|
||||||
|
|
||||||
State.Value = state;
|
|
||||||
});
|
|
||||||
|
|
||||||
#region Disposal
|
#region Disposal
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -23,17 +23,17 @@ namespace osu.Game.Online.Rooms
|
|||||||
/// The beatmap's downloading progress, null when not in <see cref="DownloadState.Downloading"/> state.
|
/// The beatmap's downloading progress, null when not in <see cref="DownloadState.Downloading"/> state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Key(1)]
|
[Key(1)]
|
||||||
public readonly double? DownloadProgress;
|
public readonly float? DownloadProgress;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public BeatmapAvailability(DownloadState state, double? downloadProgress = null)
|
public BeatmapAvailability(DownloadState state, float? downloadProgress = null)
|
||||||
{
|
{
|
||||||
State = state;
|
State = state;
|
||||||
DownloadProgress = downloadProgress;
|
DownloadProgress = downloadProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded);
|
public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded);
|
||||||
public static BeatmapAvailability Downloading(double progress) => new BeatmapAvailability(DownloadState.Downloading, progress);
|
public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress);
|
||||||
public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing);
|
public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing);
|
||||||
public static BeatmapAvailability LocallyAvailable() => new BeatmapAvailability(DownloadState.LocallyAvailable);
|
public static BeatmapAvailability LocallyAvailable() => new BeatmapAvailability(DownloadState.LocallyAvailable);
|
||||||
|
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Rooms
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represent a checksum-verifying beatmap availability tracker usable for online play screens.
|
||||||
|
///
|
||||||
|
/// This differs from a regular download tracking composite as this accounts for the
|
||||||
|
/// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap.
|
||||||
|
/// </summary>
|
||||||
|
public class OnlinePlayBeatmapAvailablilityTracker : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
|
||||||
|
{
|
||||||
|
public readonly IBindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The availability state of the currently selected playlist item.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<BeatmapAvailability> Availability => availability;
|
||||||
|
|
||||||
|
private readonly Bindable<BeatmapAvailability> availability = new Bindable<BeatmapAvailability>();
|
||||||
|
|
||||||
|
public OnlinePlayBeatmapAvailablilityTracker()
|
||||||
|
{
|
||||||
|
State.BindValueChanged(_ => updateAvailability());
|
||||||
|
Progress.BindValueChanged(_ => updateAvailability(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
SelectedItem.BindValueChanged(item => Model.Value = item.NewValue?.Beatmap.Value.BeatmapSet, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet)
|
||||||
|
{
|
||||||
|
int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID;
|
||||||
|
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||||
|
|
||||||
|
var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum);
|
||||||
|
|
||||||
|
if (matchingBeatmap == null)
|
||||||
|
{
|
||||||
|
Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsModelAvailableLocally()
|
||||||
|
{
|
||||||
|
int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID;
|
||||||
|
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||||
|
|
||||||
|
var beatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum);
|
||||||
|
return beatmap?.BeatmapSet.DeletePending == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAvailability()
|
||||||
|
{
|
||||||
|
switch (State.Value)
|
||||||
|
{
|
||||||
|
case DownloadState.NotDownloaded:
|
||||||
|
availability.Value = BeatmapAvailability.NotDownloaded();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.Downloading:
|
||||||
|
availability.Value = BeatmapAvailability.Downloading((float)Progress.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.Importing:
|
||||||
|
availability.Value = BeatmapAvailability.Importing();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.LocallyAvailable:
|
||||||
|
availability.Value = BeatmapAvailability.LocallyAvailable();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(State));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,9 +40,22 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
|
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; }
|
||||||
|
|
||||||
|
protected RoomSubScreen()
|
||||||
|
{
|
||||||
|
BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||||
|
{
|
||||||
|
SelectedItem = { BindTarget = SelectedItem },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
|
AddInternal(BeatmapAvailablilityTracker);
|
||||||
|
|
||||||
sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection");
|
sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user