2019-06-12 12:06:43 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-06-11 14:19:10 +00:00
// See the LICENCE file in the repository root for full licence text.
2019-06-11 12:59:33 +00:00
using System ;
using System.Collections.Generic ;
2019-06-11 15:23:44 +00:00
using System.Linq ;
2019-06-11 12:59:33 +00:00
using System.Threading.Tasks ;
2021-09-30 09:21:16 +00:00
using Humanizer ;
2020-05-19 07:44:22 +00:00
using osu.Framework.Bindables ;
2021-09-30 09:21:16 +00:00
using osu.Framework.Logging ;
using osu.Framework.Platform ;
using osu.Game.Online.API ;
using osu.Game.Overlays.Notifications ;
2019-06-11 12:59:33 +00:00
namespace osu.Game.Database
{
2021-09-30 09:21:16 +00:00
public abstract class ModelDownloader < TModel > : IModelDownloader < TModel >
2021-10-27 11:02:51 +00:00
where TModel : class , IHasPrimaryKey , ISoftDelete , IEquatable < TModel >
2019-06-11 12:59:33 +00:00
{
2021-09-30 09:21:16 +00:00
public Action < Notification > PostNotification { protected get ; set ; }
2020-05-19 07:44:22 +00:00
public IBindable < WeakReference < ArchiveDownloadRequest < TModel > > > DownloadBegan = > downloadBegan ;
2019-06-11 12:59:33 +00:00
2020-05-19 07:44:22 +00:00
private readonly Bindable < WeakReference < ArchiveDownloadRequest < TModel > > > downloadBegan = new Bindable < WeakReference < ArchiveDownloadRequest < TModel > > > ( ) ;
public IBindable < WeakReference < ArchiveDownloadRequest < TModel > > > DownloadFailed = > downloadFailed ;
private readonly Bindable < WeakReference < ArchiveDownloadRequest < TModel > > > downloadFailed = new Bindable < WeakReference < ArchiveDownloadRequest < TModel > > > ( ) ;
2019-06-11 12:59:33 +00:00
2021-09-30 09:21:16 +00:00
private readonly IModelManager < TModel > modelManager ;
2019-06-11 12:59:33 +00:00
private readonly IAPIProvider api ;
2021-10-27 11:02:51 +00:00
protected readonly List < ArchiveDownloadRequest < TModel > > CurrentDownloads = new List < ArchiveDownloadRequest < TModel > > ( ) ;
2019-06-11 12:59:33 +00:00
2021-09-30 09:21:16 +00:00
protected ModelDownloader ( IModelManager < TModel > modelManager , IAPIProvider api , IIpcHost importHost = null )
2019-06-11 12:59:33 +00:00
{
2021-09-30 09:21:16 +00:00
this . modelManager = modelManager ;
2019-06-11 12:59:33 +00:00
this . api = api ;
}
2019-06-11 14:44:36 +00:00
/// <summary>
2019-11-17 12:48:23 +00:00
/// Creates the download request for this <typeparamref name="TModel"/>.
2019-06-11 14:44:36 +00:00
/// </summary>
2019-11-17 12:48:23 +00:00
/// <param name="model">The <typeparamref name="TModel"/> to be downloaded.</param>
2019-06-26 11:07:01 +00: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 14:44:36 +00:00
/// <returns>The request object.</returns>
2019-06-18 16:41:19 +00:00
protected abstract ArchiveDownloadRequest < TModel > CreateDownloadRequest ( TModel model , bool minimiseDownloadSize ) ;
2019-06-11 12:59:33 +00:00
2019-06-18 16:41:19 +00:00
public bool Download ( TModel model , bool minimiseDownloadSize = false )
2019-06-11 14:44:36 +00:00
{
if ( ! canDownload ( model ) ) return false ;
2019-06-18 16:41:19 +00:00
var request = CreateDownloadRequest ( model , minimiseDownloadSize ) ;
2019-06-11 14:06:08 +00:00
2019-06-11 12:59:33 +00:00
DownloadNotification notification = new DownloadNotification
{
2019-06-12 16:26:36 +00:00
Text = $"Downloading {request.Model}" ,
2019-06-11 12:59:33 +00:00
} ;
request . DownloadProgressed + = progress = >
{
notification . State = ProgressNotificationState . Active ;
notification . Progress = progress ;
} ;
request . Success + = filename = >
{
2019-06-12 12:06:43 +00:00
Task . Factory . StartNew ( async ( ) = >
2019-06-11 12:59:33 +00:00
{
2019-06-12 12:06:43 +00:00
// This gets scheduled back to the update thread, but we want the import to run in the background.
2021-09-30 09:21:16 +00:00
var imported = await modelManager . Import ( notification , new ImportTask ( filename ) ) . ConfigureAwait ( false ) ;
2019-10-28 08:41:42 +00:00
// for now a failed import will be marked as a failed download for simplicity.
if ( ! imported . Any ( ) )
2020-05-19 07:44:22 +00:00
downloadFailed . Value = new WeakReference < ArchiveDownloadRequest < TModel > > ( request ) ;
2019-10-28 08:41:42 +00:00
2021-10-27 11:02:51 +00:00
CurrentDownloads . Remove ( request ) ;
2019-06-11 12:59:33 +00:00
} , TaskCreationOptions . LongRunning ) ;
} ;
2019-10-31 06:04:13 +00:00
request . Failure + = triggerFailure ;
2019-06-11 12:59:33 +00:00
notification . CancelRequested + = ( ) = >
{
request . Cancel ( ) ;
return true ;
} ;
2021-10-27 11:02:51 +00:00
CurrentDownloads . Add ( request ) ;
2019-06-11 12:59:33 +00:00
PostNotification ? . Invoke ( notification ) ;
2019-11-29 11:03:14 +00:00
api . PerformAsync ( request ) ;
2019-06-11 12:59:33 +00:00
2020-05-19 07:44:22 +00:00
downloadBegan . Value = new WeakReference < ArchiveDownloadRequest < TModel > > ( request ) ;
2019-06-25 15:46:30 +00:00
return true ;
2019-10-31 06:04:13 +00:00
void triggerFailure ( Exception error )
{
2021-10-27 11:02:51 +00:00
CurrentDownloads . Remove ( request ) ;
2020-02-15 07:20:44 +00:00
2020-05-19 07:44:22 +00:00
downloadFailed . Value = new WeakReference < ArchiveDownloadRequest < TModel > > ( request ) ;
2019-10-31 06:04:13 +00:00
2019-12-25 19:55:14 +00:00
notification . State = ProgressNotificationState . Cancelled ;
if ( ! ( error is OperationCanceledException ) )
2021-09-30 09:21:16 +00:00
Logger . Error ( error , $"{modelManager.HumanisedModelName.Titleize()} download failed!" ) ;
2019-10-31 06:04:13 +00:00
}
2019-06-11 12:59:33 +00:00
}
2021-10-27 11:02:51 +00:00
public abstract ArchiveDownloadRequest < TModel > GetExistingDownload ( TModel model ) ;
2019-06-19 14:13:09 +00:00
2019-06-25 15:46:30 +00:00
private bool canDownload ( TModel model ) = > GetExistingDownload ( model ) = = null & & api ! = null ;
2019-06-19 14:13:09 +00:00
2019-06-11 12:59:33 +00: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 ;
}
}
}
}