2019-06-12 20:06:43 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-06-11 22:19:10 +08:00
// See the LICENCE file in the repository root for full licence text.
2019-06-11 20:59:33 +08:00
using System ;
using System.Collections.Generic ;
2019-06-11 23:23:44 +08:00
using System.Linq ;
2022-08-01 19:22:54 +08:00
using System.Net ;
2019-06-11 20:59:33 +08:00
using System.Threading.Tasks ;
2021-09-30 17:21:16 +08:00
using Humanizer ;
using osu.Framework.Logging ;
2021-11-09 20:34:56 +08:00
using osu.Game.Extensions ;
2021-09-30 17:21:16 +08:00
using osu.Game.Online.API ;
using osu.Game.Overlays.Notifications ;
2019-06-11 20:59:33 +08:00
namespace osu.Game.Database
{
2021-11-05 16:37:05 +08:00
public abstract class ModelDownloader < TModel , T > : IModelDownloader < T >
2021-11-24 11:16:08 +08:00
where TModel : class , IHasGuidPrimaryKey , ISoftDelete , IEquatable < TModel > , T
2021-11-05 16:37:05 +08:00
where T : class
2019-06-11 20:59:33 +08:00
{
2022-07-19 19:06:19 +08:00
public Action < Notification > ? PostNotification { protected get ; set ; }
2021-09-30 17:21:16 +08:00
2022-07-19 19:06:19 +08:00
public event Action < ArchiveDownloadRequest < T > > ? DownloadBegan ;
2019-06-11 20:59:33 +08:00
2022-07-19 19:06:19 +08:00
public event Action < ArchiveDownloadRequest < T > > ? DownloadFailed ;
2019-06-11 20:59:33 +08:00
2021-11-05 15:46:40 +08:00
private readonly IModelImporter < TModel > importer ;
2022-07-19 19:06:19 +08:00
private readonly IAPIProvider ? api ;
2019-06-11 20:59:33 +08:00
2021-11-05 16:37:05 +08:00
protected readonly List < ArchiveDownloadRequest < T > > CurrentDownloads = new List < ArchiveDownloadRequest < T > > ( ) ;
2019-06-11 20:59:33 +08:00
2022-07-19 19:06:19 +08:00
protected ModelDownloader ( IModelImporter < TModel > importer , IAPIProvider ? api )
2019-06-11 20:59:33 +08:00
{
2021-11-05 15:46:40 +08:00
this . importer = importer ;
2019-06-11 20:59:33 +08:00
this . api = api ;
}
2019-06-11 22:44:36 +08:00
/// <summary>
2021-11-05 16:37:05 +08:00
/// Creates the download request for this <typeparamref name="T"/>.
2019-06-11 22:44:36 +08:00
/// </summary>
2021-11-05 16:37:05 +08:00
/// <param name="model">The <typeparamref name="T"/> to be downloaded.</param>
2019-06-26 19:07:01 +08:00
/// <param name="minimiseDownloadSize">Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.</param>
2019-06-11 22:44:36 +08:00
/// <returns>The request object.</returns>
2021-11-05 16:37:05 +08:00
protected abstract ArchiveDownloadRequest < T > CreateDownloadRequest ( T model , bool minimiseDownloadSize ) ;
2019-06-11 20:59:33 +08:00
2022-07-25 17:52:53 +08:00
public bool Download ( T model , bool minimiseDownloadSize = false ) = > Download ( model , minimiseDownloadSize , null ) ;
2022-10-11 10:29:17 +08:00
public void DownloadAsUpdate ( TModel originalModel , bool minimiseDownloadSize ) = > Download ( originalModel , minimiseDownloadSize , originalModel ) ;
2022-07-26 14:46:29 +08:00
2022-07-25 18:31:46 +08:00
protected bool Download ( T model , bool minimiseDownloadSize , TModel ? originalModel )
2019-06-11 22:44:36 +08:00
{
if ( ! canDownload ( model ) ) return false ;
2019-06-19 00:41:19 +08:00
var request = CreateDownloadRequest ( model , minimiseDownloadSize ) ;
2019-06-11 22:06:08 +08:00
2019-06-11 20:59:33 +08:00
DownloadNotification notification = new DownloadNotification
{
2021-11-09 20:34:56 +08:00
Text = $"Downloading {request.Model.GetDisplayString()}" ,
2019-06-11 20:59:33 +08:00
} ;
request . DownloadProgressed + = progress = >
{
notification . State = ProgressNotificationState . Active ;
notification . Progress = progress ;
} ;
request . Success + = filename = >
{
2019-06-12 20:06:43 +08:00
Task . Factory . StartNew ( async ( ) = >
2019-06-11 20:59:33 +08:00
{
2022-07-26 16:58:09 +08:00
bool importSuccessful ;
if ( originalModel ! = null )
importSuccessful = ( await importer . ImportAsUpdate ( notification , new ImportTask ( filename ) , originalModel ) ) ! = null ;
else
importSuccessful = ( await importer . Import ( notification , new ImportTask ( filename ) ) ) . Any ( ) ;
2019-10-28 16:41:42 +08:00
// for now a failed import will be marked as a failed download for simplicity.
2022-07-26 16:58:09 +08:00
if ( ! importSuccessful )
2021-11-05 17:05:31 +08:00
DownloadFailed ? . Invoke ( request ) ;
2019-10-28 16:41:42 +08:00
2021-10-27 19:02:51 +08:00
CurrentDownloads . Remove ( request ) ;
2019-06-11 20:59:33 +08:00
} , TaskCreationOptions . LongRunning ) ;
} ;
2019-10-31 14:04:13 +08:00
request . Failure + = triggerFailure ;
2019-06-11 20:59:33 +08:00
notification . CancelRequested + = ( ) = >
{
request . Cancel ( ) ;
return true ;
} ;
2021-10-27 19:02:51 +08:00
CurrentDownloads . Add ( request ) ;
2019-06-11 20:59:33 +08:00
PostNotification ? . Invoke ( notification ) ;
2022-07-19 19:06:19 +08:00
api ? . PerformAsync ( request ) ;
2019-06-11 20:59:33 +08:00
2021-11-05 17:05:31 +08:00
DownloadBegan ? . Invoke ( request ) ;
2019-06-25 23:46:30 +08:00
return true ;
2019-10-31 14:04:13 +08:00
void triggerFailure ( Exception error )
{
2021-10-27 19:02:51 +08:00
CurrentDownloads . Remove ( request ) ;
2020-02-15 15:20:44 +08:00
2021-11-05 17:05:31 +08:00
DownloadFailed ? . Invoke ( request ) ;
2019-10-31 14:04:13 +08:00
2019-12-26 03:55:14 +08:00
notification . State = ProgressNotificationState . Cancelled ;
if ( ! ( error is OperationCanceledException ) )
2022-08-01 19:22:54 +08:00
{
if ( error is WebException webException & & webException . Message = = @"TooManyRequests" )
{
2022-09-12 15:54:25 +08:00
notification . Close ( false ) ;
2022-08-03 19:15:42 +08:00
PostNotification ? . Invoke ( new TooManyDownloadsNotification ( ) ) ;
2022-08-01 19:22:54 +08:00
}
else
Logger . Error ( error , $"{importer.HumanisedModelName.Titleize()} download failed!" ) ;
}
2019-10-31 14:04:13 +08:00
}
2019-06-11 20:59:33 +08:00
}
2022-07-19 19:06:19 +08:00
public abstract ArchiveDownloadRequest < T > ? GetExistingDownload ( T model ) ;
2019-06-19 22:13:09 +08:00
2021-11-05 16:37:05 +08:00
private bool canDownload ( T model ) = > GetExistingDownload ( model ) = = null & & api ! = null ;
2019-06-19 22:13:09 +08:00
2019-06-11 20:59:33 +08:00
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 ;
}
}
}
}