2021-11-29 17:07:32 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using osu.Framework.Platform ;
using osu.Game.Database ;
using osu.Game.IO.Archives ;
using osu.Game.Models ;
using osu.Game.Overlays.Notifications ;
using Realms ;
#nullable enable
namespace osu.Game.Stores
{
/// <summary>
/// Class which adds all the missing pieces bridging the gap between <see cref="RealmArchiveModelImporter{TModel}"/> and <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
/// </summary>
public abstract class RealmArchiveModelManager < TModel > : RealmArchiveModelImporter < TModel > , IModelManager < TModel > , IModelFileManager < TModel , RealmNamedFileUsage >
where TModel : RealmObject , IHasRealmFiles , IHasGuidPrimaryKey , ISoftDelete
{
2021-11-29 15:18:34 +08:00
public event Action < TModel > ? ItemUpdated
{
// This may be brought back for beatmaps to ease integration.
// The eventual goal would be not requiring this and using realm subscriptions in its place.
add = > throw new NotImplementedException ( ) ;
remove = > throw new NotImplementedException ( ) ;
}
public event Action < TModel > ? ItemRemoved
{
// This may be brought back for beatmaps to ease integration.
// The eventual goal would be not requiring this and using realm subscriptions in its place.
add = > throw new NotImplementedException ( ) ;
remove = > throw new NotImplementedException ( ) ;
}
2021-11-29 17:07:32 +08:00
private readonly RealmFileStore realmFileStore ;
protected RealmArchiveModelManager ( Storage storage , RealmContextFactory contextFactory )
: base ( storage , contextFactory )
{
realmFileStore = new RealmFileStore ( contextFactory , storage ) ;
}
public void DeleteFile ( TModel item , RealmNamedFileUsage file ) = >
item . Realm . Write ( ( ) = > DeleteFile ( item , file , item . Realm ) ) ;
public void ReplaceFile ( TModel item , RealmNamedFileUsage file , Stream contents )
2021-12-02 16:19:53 +08:00
= > item . Realm . Write ( ( ) = > ReplaceFile ( file , contents , item . Realm ) ) ;
2021-11-29 17:07:32 +08:00
2021-12-02 16:19:53 +08:00
public void AddFile ( TModel item , Stream contents , string filename )
= > item . Realm . Write ( ( ) = > AddFile ( item , contents , filename , item . Realm ) ) ;
2021-11-29 17:07:32 +08:00
2021-12-01 11:55:19 +08:00
/// <summary>
/// Delete a file from within an ongoing realm transaction.
/// </summary>
protected void DeleteFile ( TModel item , RealmNamedFileUsage file , Realm realm )
2021-11-29 17:07:32 +08:00
{
item . Files . Remove ( file ) ;
}
2021-12-01 11:55:19 +08:00
/// <summary>
/// Replace a file from within an ongoing realm transaction.
/// </summary>
2021-12-02 16:19:53 +08:00
protected void ReplaceFile ( RealmNamedFileUsage file , Stream contents , Realm realm )
2021-11-29 17:07:32 +08:00
{
file . File = realmFileStore . Add ( contents , realm ) ;
}
2021-12-01 11:55:19 +08:00
/// <summary>
2021-12-02 16:17:12 +08:00
/// Add a file from within an ongoing realm transaction. If the file already exists, it is overwritten.
2021-12-01 11:55:19 +08:00
/// </summary>
2021-12-02 16:19:53 +08:00
protected void AddFile ( TModel item , Stream contents , string filename , Realm realm )
2021-11-29 17:07:32 +08:00
{
2021-12-02 16:17:12 +08:00
var existing = item . Files . FirstOrDefault ( f = > string . Equals ( f . Filename , filename , StringComparison . OrdinalIgnoreCase ) ) ;
if ( existing ! = null )
{
2021-12-02 16:19:53 +08:00
ReplaceFile ( existing , contents , realm ) ;
2021-12-02 16:17:12 +08:00
return ;
}
2021-12-02 16:19:53 +08:00
var file = realmFileStore . Add ( contents , realm ) ;
2021-11-29 17:07:32 +08:00
var namedUsage = new RealmNamedFileUsage ( file , filename ) ;
item . Files . Add ( namedUsage ) ;
}
public override async Task < ILive < TModel > ? > Import ( TModel item , ArchiveReader ? archive = null , bool lowPriority = false , CancellationToken cancellationToken = default )
{
2021-11-29 15:18:34 +08:00
return await base . Import ( item , archive , lowPriority , cancellationToken ) . ConfigureAwait ( false ) ;
2021-11-29 17:07:32 +08:00
}
/// <summary>
/// Delete multiple items.
/// This will post notifications tracking progress.
/// </summary>
public void Delete ( List < TModel > items , bool silent = false )
{
if ( items . Count = = 0 ) return ;
var notification = new ProgressNotification
{
Progress = 0 ,
Text = $"Preparing to delete all {HumanisedModelName}s..." ,
CompletionText = $"Deleted all {HumanisedModelName}s!" ,
State = ProgressNotificationState . Active ,
} ;
if ( ! silent )
PostNotification ? . Invoke ( notification ) ;
int i = 0 ;
foreach ( var b in items )
{
if ( notification . State = = ProgressNotificationState . Cancelled )
// user requested abort
return ;
notification . Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})" ;
Delete ( b ) ;
notification . Progress = ( float ) i / items . Count ;
}
notification . State = ProgressNotificationState . Completed ;
}
/// <summary>
/// Restore multiple items that were previously deleted.
/// This will post notifications tracking progress.
/// </summary>
public void Undelete ( List < TModel > items , bool silent = false )
{
if ( ! items . Any ( ) ) return ;
var notification = new ProgressNotification
{
CompletionText = "Restored all deleted items!" ,
Progress = 0 ,
State = ProgressNotificationState . Active ,
} ;
if ( ! silent )
PostNotification ? . Invoke ( notification ) ;
int i = 0 ;
foreach ( var item in items )
{
if ( notification . State = = ProgressNotificationState . Cancelled )
// user requested abort
return ;
notification . Text = $"Restoring ({++i} of {items.Count})" ;
Undelete ( item ) ;
notification . Progress = ( float ) i / items . Count ;
}
notification . State = ProgressNotificationState . Completed ;
}
public bool Delete ( TModel item )
{
if ( item . DeletePending )
return false ;
item . Realm . Write ( r = > item . DeletePending = true ) ;
return true ;
}
public void Undelete ( TModel item )
{
if ( ! item . DeletePending )
return ;
item . Realm . Write ( r = > item . DeletePending = false ) ;
}
2021-11-29 15:18:34 +08:00
public virtual bool IsAvailableLocally ( TModel model ) = > false ; // Not relevant for skins since they can't be downloaded yet.
2021-11-29 17:07:32 +08:00
public void Update ( TModel skin )
{
}
}
}