// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Online.API; using osu.Game.Overlays.Notifications; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace osu.Game.Database { /// /// An that has the ability to download models using an and /// import them into the store. /// /// The model type. /// The associated file join type. /// The associated for this model. public abstract class ArchiveDownloadModelManager : ArchiveModelManager where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() where TDownloadRequestModel : ArchiveDownloadModelRequest { /// /// Fired when a download begins. /// public event Action DownloadBegan; /// /// Fired when a download is interrupted, either due to user cancellation or failure. /// public event Action DownloadFailed; private readonly IAPIProvider api; private readonly List currentDownloads = new List(); protected ArchiveDownloadModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) : base(storage, contextFactory, modelStore, importHost) { this.api = api; } /// /// Creates the download request for this . /// The parameters will be provided when the download was initiated with extra options meant /// to be used in the creation of the request. /// /// The to be downloaded. /// Extra parameters for request creation, null if none were passed. /// The request object. protected abstract TDownloadRequestModel CreateDownloadRequest(TModel model, object[] options); /// /// Downloads a . /// This will post notifications tracking progress. /// /// The to be downloaded. /// Whether downloading can happen. public bool Download(TModel model) { if (!canDownload(model)) return false; var request = CreateDownloadRequest(model, null); performDownloadWithRequest(request); return true; } /// /// Downloads a with optional parameters for the download request. /// /// The to be downloaded. /// Optional parameters to be used for creating the download request. /// Whether downloading can happen. public bool Download(TModel model, params object[] extra) { if (!canDownload(model)) return false; var request = CreateDownloadRequest(model, extra); performDownloadWithRequest(request); return true; } /// /// Gets an existing download request if it exists. /// /// The whose request is wanted. /// The object if it exists, otherwise null. public TDownloadRequestModel GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Info.Equals(model)); private bool canDownload(TModel model) => GetExistingDownload(model) == null && api != null; private void performDownloadWithRequest(TDownloadRequestModel request) { DownloadNotification notification = new DownloadNotification { Text = $"Downloading {request.Info}", }; request.DownloadProgressed += progress => { notification.State = ProgressNotificationState.Active; notification.Progress = progress; }; request.Success += filename => { Task.Factory.StartNew(() => { Import(notification, filename); currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; request.Failure += error => { DownloadFailed?.Invoke(request); if (error is OperationCanceledException) return; notification.State = ProgressNotificationState.Cancelled; // TODO: maybe implement a Name for every model that we can use in this message? Logger.Error(error, "Download failed!"); currentDownloads.Remove(request); }; notification.CancelRequested += () => { request.Cancel(); currentDownloads.Remove(request); notification.State = ProgressNotificationState.Cancelled; return true; }; currentDownloads.Add(request); PostNotification?.Invoke(notification); Task.Factory.StartNew(() => { try { request.Perform(api); } catch { } }, TaskCreationOptions.LongRunning); DownloadBegan?.Invoke(request); } private class DownloadNotification : ProgressNotification { public override bool IsImportant => false; protected override Notification CreateCompletionNotification() => new SilencedProgressCompletionNotification { Activated = CompletionClickAction, Text = CompletionText }; private class SilencedProgressCompletionNotification : ProgressCompletionNotification { public override bool IsImportant => false; } } } }