// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Online.API; namespace osu.Game.Online { /// /// A component which tracks a through potential download/import/deletion. /// public abstract class DownloadTrackingComposite : CompositeDrawable where TModel : class, IEquatable where TModelManager : class, IModelDownloader { protected readonly Bindable Model = new Bindable(); [Resolved(CanBeNull = true)] protected TModelManager Manager { get; private set; } /// /// Holds the current download state of the , whether is has already been downloaded, is in progress, or is not downloaded. /// protected readonly Bindable State = new Bindable(); protected readonly BindableNumber Progress = new BindableNumber { MinValue = 0, MaxValue = 1 }; protected DownloadTrackingComposite(TModel model = null) { Model.Value = model; } private IBindable> managedUpdated; private IBindable> managerRemoved; private IBindable>> managerDownloadBegan; private IBindable>> managerDownloadFailed; [BackgroundDependencyLoader(true)] private void load() { Model.BindValueChanged(modelInfo => { if (modelInfo.NewValue == null) attachDownload(null); else if (IsModelAvailableLocally()) State.Value = DownloadState.LocallyAvailable; else attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue)); }, true); if (Manager == null) return; managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); managerDownloadBegan.BindValueChanged(downloadBegan); managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); managerDownloadFailed.BindValueChanged(downloadFailed); managedUpdated = Manager.ItemUpdated.GetBoundCopy(); managedUpdated.BindValueChanged(itemUpdated); managerRemoved = Manager.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); } /// /// Verifies that the given databased model is in a correct state to be considered available. /// /// /// In the case of multiplayer/playlists, this has to verify that the databased beatmap set with the selected beatmap matches what's online. /// /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; /// /// Whether the given model is available in the database. /// By default, this calls , /// but can be overriden to add additional checks for verifying the model in database. /// protected virtual bool IsModelAvailableLocally() => Manager?.IsAvailableLocally(Model.Value) == true; private void downloadBegan(ValueChangedEvent>> weakRequest) { if (weakRequest.NewValue.TryGetTarget(out var request)) { Schedule(() => { if (request.Model.Equals(Model.Value)) attachDownload(request); }); } } private void downloadFailed(ValueChangedEvent>> weakRequest) { if (weakRequest.NewValue.TryGetTarget(out var request)) { Schedule(() => { if (request.Model.Equals(Model.Value)) attachDownload(null); }); } } private ArchiveDownloadRequest attachedRequest; private void attachDownload(ArchiveDownloadRequest request) { if (attachedRequest != null) { attachedRequest.Failure -= onRequestFailure; attachedRequest.DownloadProgressed -= onRequestProgress; attachedRequest.Success -= onRequestSuccess; } attachedRequest = request; if (attachedRequest != null) { if (attachedRequest.Progress == 1) { State.Value = DownloadState.Importing; Progress.Value = 1; } else { State.Value = DownloadState.Downloading; Progress.Value = attachedRequest.Progress; attachedRequest.Failure += onRequestFailure; attachedRequest.DownloadProgressed += onRequestProgress; attachedRequest.Success += onRequestSuccess; } } else { State.Value = DownloadState.NotDownloaded; } } private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing); private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) { Schedule(() => { if (!item.Equals(Model.Value)) return; if (!VerifyDatabasedModel(item)) { State.Value = DownloadState.NotDownloaded; return; } State.Value = DownloadState.LocallyAvailable; }); } } private void itemRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) { Schedule(() => { if (item.Equals(Model.Value)) State.Value = DownloadState.NotDownloaded; }); } } #region Disposal protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); State.UnbindAll(); attachDownload(null); } #endregion } }