2019-06-12 17:36:43 +05:30
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-06-11 19:49:10 +05:30
// See the LICENCE file in the repository root for full licence text.
2019-06-12 18:35:34 +05:30
using Humanizer ;
2019-06-11 19:49:10 +05:30
using osu.Framework.Logging ;
2019-06-11 18:29:33 +05:30
using osu.Framework.Platform ;
using osu.Game.Online.API ;
using osu.Game.Overlays.Notifications ;
using System ;
using System.Collections.Generic ;
2019-06-11 20:53:44 +05:30
using System.Linq ;
2019-06-11 18:29:33 +05:30
using System.Threading.Tasks ;
2020-05-19 16:44:22 +09:00
using osu.Framework.Bindables ;
2019-06-11 18:29:33 +05:30
namespace osu.Game.Database
{
2019-06-11 19:49:10 +05:30
/// <summary>
/// An <see cref="ArchiveModelManager{TModel, TFileModel}"/> that has the ability to download models using an <see cref="IAPIProvider"/> and
/// import them into the store.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
2019-06-12 13:28:44 +09:00
public abstract class DownloadableArchiveModelManager < TModel , TFileModel > : ArchiveModelManager < TModel , TFileModel > , IModelDownloader < TModel >
2019-06-27 16:53:44 +09:00
where TModel : class , IHasFiles < TFileModel > , IHasPrimaryKey , ISoftDelete , IEquatable < TModel >
2020-01-14 18:43:06 +09:00
where TFileModel : class , INamedFileInfo , new ( )
2019-06-11 18:29:33 +05:30
{
2020-05-19 16:44:22 +09:00
public IBindable < WeakReference < ArchiveDownloadRequest < TModel > > > DownloadBegan = > downloadBegan ;
2019-06-11 18:29:33 +05:30
2020-05-19 16:44:22 +09: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 18:29:33 +05:30
private readonly IAPIProvider api ;
2019-06-12 13:30:23 +09:00
private readonly List < ArchiveDownloadRequest < TModel > > currentDownloads = new List < ArchiveDownloadRequest < TModel > > ( ) ;
2019-06-11 18:29:33 +05:30
2019-06-11 20:53:44 +05:30
private readonly MutableDatabaseBackedStoreWithFileIncludes < TModel , TFileModel > modelStore ;
2019-12-17 15:34:16 +09:00
protected DownloadableArchiveModelManager ( Storage storage , IDatabaseContextFactory contextFactory , IAPIProvider api , MutableDatabaseBackedStoreWithFileIncludes < TModel , TFileModel > modelStore ,
IIpcHost importHost = null )
2019-06-11 20:21:06 +05:30
: base ( storage , contextFactory , modelStore , importHost )
2019-06-11 18:29:33 +05:30
{
this . api = api ;
2019-06-11 20:53:44 +05:30
this . modelStore = modelStore ;
2019-06-11 18:29:33 +05:30
}
2019-06-11 20:14:36 +05:30
/// <summary>
2019-11-17 20:48:23 +08:00
/// Creates the download request for this <typeparamref name="TModel"/>.
2019-06-11 20:14:36 +05:30
/// </summary>
2019-11-17 20:48:23 +08:00
/// <param name="model">The <typeparamref name="TModel"/> to be downloaded.</param>
2019-06-26 20:07:01 +09: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 20:14:36 +05:30
/// <returns>The request object.</returns>
2019-06-19 01:41:19 +09:00
protected abstract ArchiveDownloadRequest < TModel > CreateDownloadRequest ( TModel model , bool minimiseDownloadSize ) ;
2019-06-11 18:29:33 +05:30
2019-06-19 01:41:19 +09:00
/// <summary>
2019-11-17 20:48:23 +08:00
/// Begin a download for the requested <typeparamref name="TModel"/>.
2019-06-19 01:41:19 +09:00
/// </summary>
2019-11-17 20:48:23 +08:00
/// <param name="model">The <typeparamref name="TModel"/> to be downloaded.</param>
2019-06-26 20:07:01 +09: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-19 01:41:19 +09:00
/// <returns>Whether the download was started.</returns>
public bool Download ( TModel model , bool minimiseDownloadSize = false )
2019-06-11 20:14:36 +05:30
{
if ( ! canDownload ( model ) ) return false ;
2019-06-19 01:41:19 +09:00
var request = CreateDownloadRequest ( model , minimiseDownloadSize ) ;
2019-06-11 19:36:08 +05:30
2019-06-11 18:29:33 +05:30
DownloadNotification notification = new DownloadNotification
{
2019-06-12 21:56:36 +05:30
Text = $"Downloading {request.Model}" ,
2019-06-11 18:29:33 +05:30
} ;
request . DownloadProgressed + = progress = >
{
notification . State = ProgressNotificationState . Active ;
notification . Progress = progress ;
} ;
request . Success + = filename = >
{
2019-06-12 17:36:43 +05:30
Task . Factory . StartNew ( async ( ) = >
2019-06-11 18:29:33 +05:30
{
2019-06-12 17:36:43 +05:30
// This gets scheduled back to the update thread, but we want the import to run in the background.
2019-10-28 17:41:42 +09:00
var imported = await Import ( notification , filename ) ;
// for now a failed import will be marked as a failed download for simplicity.
if ( ! imported . Any ( ) )
2020-05-19 16:44:22 +09:00
downloadFailed . Value = new WeakReference < ArchiveDownloadRequest < TModel > > ( request ) ;
2019-10-28 17:41:42 +09:00
2019-06-11 18:29:33 +05:30
currentDownloads . Remove ( request ) ;
} , TaskCreationOptions . LongRunning ) ;
} ;
2019-10-31 15:04:13 +09:00
request . Failure + = triggerFailure ;
2019-06-11 18:29:33 +05:30
notification . CancelRequested + = ( ) = >
{
request . Cancel ( ) ;
return true ;
} ;
currentDownloads . Add ( request ) ;
PostNotification ? . Invoke ( notification ) ;
2019-11-29 20:03:14 +09:00
api . PerformAsync ( request ) ;
2019-06-11 18:29:33 +05:30
2020-05-19 16:44:22 +09:00
downloadBegan . Value = new WeakReference < ArchiveDownloadRequest < TModel > > ( request ) ;
2019-06-25 21:16:30 +05:30
return true ;
2019-10-31 15:04:13 +09:00
void triggerFailure ( Exception error )
{
2020-02-15 16:20:44 +09:00
currentDownloads . Remove ( request ) ;
2020-05-19 16:44:22 +09:00
downloadFailed . Value = new WeakReference < ArchiveDownloadRequest < TModel > > ( request ) ;
2019-10-31 15:04:13 +09:00
2019-12-25 22:55:14 +03:00
notification . State = ProgressNotificationState . Cancelled ;
if ( ! ( error is OperationCanceledException ) )
Logger . Error ( error , $"{HumanisedModelName.Titleize()} download failed!" ) ;
2019-10-31 15:04:13 +09:00
}
2019-06-11 18:29:33 +05:30
}
2019-06-27 15:14:57 +05:30
public bool IsAvailableLocally ( TModel model ) = > CheckLocalAvailability ( model , modelStore . ConsumableItems . Where ( m = > ! m . DeletePending ) ) ;
/// <summary>
/// Performs implementation specific comparisons to determine whether a given model is present in the local store.
/// </summary>
2019-11-17 20:48:23 +08:00
/// <param name="model">The <typeparamref name="TModel"/> whose existence needs to be checked.</param>
2019-06-27 15:14:57 +05:30
/// <param name="items">The usable items present in the store.</param>
2019-11-17 20:48:23 +08:00
/// <returns>Whether the <typeparamref name="TModel"/> exists.</returns>
2019-12-17 15:34:16 +09:00
protected virtual bool CheckLocalAvailability ( TModel model , IQueryable < TModel > items )
= > model . ID > 0 & & items . Any ( i = > i . ID = = model . ID & & i . Files . Any ( ) ) ;
2019-06-19 19:43:09 +05:30
2019-06-25 21:16:30 +05:30
public ArchiveDownloadRequest < TModel > GetExistingDownload ( TModel model ) = > currentDownloads . Find ( r = > r . Model . Equals ( model ) ) ;
2019-06-19 19:43:09 +05:30
2019-06-25 21:16:30 +05:30
private bool canDownload ( TModel model ) = > GetExistingDownload ( model ) = = null & & api ! = null ;
2019-06-19 19:43:09 +05:30
2019-06-11 18:29:33 +05:30
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 ;
}
}
}
}