Add import progress/success stage to dialog
This commit is contained in:
parent
cf27436a71
commit
3d991dc601
4 changed files with 138 additions and 50 deletions
48
LANCommander.SDK/AsyncEventHandler.cs
Normal file
48
LANCommander.SDK/AsyncEventHandler.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.SDK;
|
||||
|
||||
public class AsyncEventHandler<T>
|
||||
{
|
||||
public event Func<T, Task>? EventRaised;
|
||||
|
||||
public async Task InvokeAsync(T args)
|
||||
{
|
||||
if (EventRaised is null)
|
||||
return;
|
||||
|
||||
var subscribers = EventRaised.GetInvocationList()
|
||||
.Cast<Func<T, Task>>();
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var sub in subscribers)
|
||||
tasks.Add(SafeInvoke(sub, args));
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public async Task InvokeSequentialAsync(T args)
|
||||
{
|
||||
if (EventRaised is null)
|
||||
return;
|
||||
|
||||
var subscribers = EventRaised.GetInvocationList()
|
||||
.Cast<Func<T, Task>>();
|
||||
|
||||
foreach (var sub in subscribers)
|
||||
await SafeInvoke(sub, args);
|
||||
}
|
||||
|
||||
private async Task SafeInvoke(Func<T, Task> subscriber, T args)
|
||||
{
|
||||
try {
|
||||
await subscriber.Invoke(args);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Console.WriteLine($"Async event subscriber failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.Server.Data.Models;
|
||||
|
|
@ -26,10 +27,10 @@ public class ImportContext : IDisposable
|
|||
private Queue<IImportItemInfo> Queue { get; } = new();
|
||||
private IEnumerable<Guid> SelectedRecordIds { get; set; } = [];
|
||||
|
||||
public EventHandler<ImportStatusUpdate> OnImportStarted { get; set; }
|
||||
public EventHandler<ImportStatusUpdate> OnImportStatusUpdate { get; set; }
|
||||
public EventHandler<ImportStatusUpdate> OnImportComplete { get; set; }
|
||||
public EventHandler<ImportStatusUpdate> OnImportError;
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportStarted { get; set; } = new();
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportStatusUpdate { get; set; } = new();
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportComplete { get; set; } = new();
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportError = new();
|
||||
|
||||
private readonly ImportService _importService;
|
||||
private readonly StorageLocationService _storageLocationService;
|
||||
|
|
@ -272,11 +273,11 @@ public class ImportContext : IDisposable
|
|||
|
||||
public async Task ImportQueueAsync()
|
||||
{
|
||||
OnImportStarted?.Invoke(this, new ImportStatusUpdate
|
||||
await OnImportStarted?.InvokeAsync(new ImportStatusUpdate
|
||||
{
|
||||
Index = -1,
|
||||
Total = Queue.Count,
|
||||
});
|
||||
})!;
|
||||
|
||||
int deferred = 0;
|
||||
|
||||
|
|
@ -284,12 +285,12 @@ public class ImportContext : IDisposable
|
|||
{
|
||||
var queueItem = Queue.Dequeue();
|
||||
|
||||
OnImportStatusUpdate?.Invoke(this, new ImportStatusUpdate
|
||||
await OnImportStatusUpdate?.InvokeAsync(new ImportStatusUpdate
|
||||
{
|
||||
CurrentItem = queueItem,
|
||||
Index = Processed,
|
||||
Total = Total,
|
||||
});
|
||||
})!;
|
||||
|
||||
var success = await TryImportAsync(queueItem);
|
||||
|
||||
|
|
@ -306,11 +307,11 @@ public class ImportContext : IDisposable
|
|||
throw new InvalidOperationException("Import deadlocked: remaining jobs cannot be satisfied.");
|
||||
}
|
||||
|
||||
OnImportComplete?.Invoke(this, new ImportStatusUpdate
|
||||
await OnImportComplete?.InvokeAsync(new ImportStatusUpdate
|
||||
{
|
||||
Index = Total - 1,
|
||||
Total = Total,
|
||||
});
|
||||
})!;
|
||||
|
||||
_importService.RemoveContext(Id.Value);
|
||||
}
|
||||
|
|
@ -392,13 +393,13 @@ public class ImportContext : IDisposable
|
|||
{
|
||||
_logger.LogError(ex, "Error importing record {RecordName}", queueItem.Name);
|
||||
|
||||
OnImportError?.Invoke(this, new ImportStatusUpdate
|
||||
await OnImportError?.InvokeAsync(new ImportStatusUpdate
|
||||
{
|
||||
CurrentItem = CurrentItem,
|
||||
Index = Processed,
|
||||
Total = Total,
|
||||
Error = ex.Message,
|
||||
});
|
||||
})!;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
using LANCommander.Server.ImportExport.Importers;
|
||||
using LANCommander.SDK;
|
||||
using LANCommander.Server.ImportExport.Models;
|
||||
|
||||
namespace LANCommander.Server.ImportExport.Services;
|
||||
|
||||
public class ImportService : IDisposable
|
||||
{
|
||||
public EventHandler<ImportStatusUpdate> OnImportStarted;
|
||||
public EventHandler<ImportStatusUpdate> OnImportComplete;
|
||||
public EventHandler<ImportStatusUpdate> OnImportStatusUpdate;
|
||||
public EventHandler<ImportStatusUpdate> OnImportError;
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportStarted = new();
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportComplete = new();
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportStatusUpdate = new();
|
||||
public AsyncEventHandler<ImportStatusUpdate> OnImportError = new();
|
||||
|
||||
private Dictionary<Guid, ImportContext> _importContexts = new();
|
||||
|
||||
|
|
@ -20,10 +20,10 @@ public class ImportService : IDisposable
|
|||
|
||||
_importContexts.Add(id, context);
|
||||
|
||||
context.OnImportStarted += OnImportStarted;
|
||||
context.OnImportComplete += OnImportComplete;
|
||||
context.OnImportStatusUpdate += OnImportStatusUpdate;
|
||||
context.OnImportError += OnImportError;
|
||||
context.OnImportStarted = OnImportStarted;
|
||||
context.OnImportComplete = OnImportComplete;
|
||||
context.OnImportStatusUpdate = OnImportStatusUpdate;
|
||||
context.OnImportError = OnImportError;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
@using LANCommander.Server.ImportExport
|
||||
@using LANCommander.Server.ImportExport.Factories
|
||||
@using LANCommander.Server.ImportExport.Models
|
||||
@using LANCommander.Server.ImportExport.Services
|
||||
@inherits FeedbackComponent<ImportDialogOptions>
|
||||
@inject GameService GameService
|
||||
@inject ArchiveService ArchiveService
|
||||
@inject StorageLocationService StorageLocationService
|
||||
@inject ImportContextFactory ImportContextFactory
|
||||
|
|
@ -76,7 +74,7 @@ else if (_stage == ImportStage.RecordSelection)
|
|||
<ChildContent>
|
||||
@foreach (var item in group)
|
||||
{
|
||||
<TreeNode Key="@item.Id.ToString()">
|
||||
<TreeNode Key="@item.Key">
|
||||
<TitleTemplate>
|
||||
<GridRow>
|
||||
<GridCol Flex=@("auto")>
|
||||
|
|
@ -105,7 +103,24 @@ else if (_stage == ImportStage.RecordSelection)
|
|||
}
|
||||
else if (_stage == ImportStage.Importing)
|
||||
{
|
||||
<Progress Type="ProgressType.Circle" Percent="_progress" />
|
||||
<Flex Align="FlexAlign.Center" Direction="FlexDirection.Vertical" Gap="FlexGap.Middle">
|
||||
<Progress Type="ProgressType.Circle" Percent="_importProgress" />
|
||||
|
||||
<Flex Gap="FlexGap.Small" Direction="FlexDirection.Vertical">
|
||||
@foreach (var error in _errors)
|
||||
{
|
||||
<Alert Type="AlertType.Error" Message="@error" />
|
||||
}
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
else if (_stage == ImportStage.Complete)
|
||||
{
|
||||
<Result Status="ResultStatus.Success" Title="Import Complete!">
|
||||
<Extra>
|
||||
<Button Type="ButtonType.Primary" OnClick="CloseFeedbackAsync">Close</Button>
|
||||
</Extra>
|
||||
</Result>
|
||||
}
|
||||
|
||||
@code {
|
||||
|
|
@ -113,7 +128,8 @@ else if (_stage == ImportStage.Importing)
|
|||
{
|
||||
Upload,
|
||||
RecordSelection,
|
||||
Importing
|
||||
Importing,
|
||||
Complete,
|
||||
};
|
||||
|
||||
IBrowserFile File;
|
||||
|
|
@ -121,7 +137,7 @@ else if (_stage == ImportStage.Importing)
|
|||
StorageLocation StorageLocation = new();
|
||||
ImportContext ImportContext;
|
||||
|
||||
IEnumerable<ImportItemInfo> ImportItems = new List<ImportItemInfo>();
|
||||
IEnumerable<IImportItemInfo> ImportItems = new List<IImportItemInfo>();
|
||||
|
||||
string RootPath = Path.GetPathRoot(Directory.GetCurrentDirectory());
|
||||
|
||||
|
|
@ -131,37 +147,64 @@ else if (_stage == ImportStage.Importing)
|
|||
|
||||
ImportStage _stage = ImportStage.Upload;
|
||||
string[] _selectedKeys;
|
||||
int _progress {
|
||||
get
|
||||
{
|
||||
return ImportContext.Processed / ImportContext.Total;
|
||||
}
|
||||
}
|
||||
|
||||
List<string> _errors = new();
|
||||
|
||||
int _importProgress = -1;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
ImportContext = ImportContextFactory.Create();
|
||||
|
||||
ImportContext.OnRecordError += RecordError;
|
||||
ImportContext.OnRecordAdded += RecordAdded;
|
||||
ImportContext.OnRecordProcessed += RecordProcessed;
|
||||
|
||||
ImportContext.OnImportStarted.EventRaised += ImportStarted;
|
||||
ImportContext.OnImportComplete.EventRaised += ImportComplete;
|
||||
ImportContext.OnImportStatusUpdate.EventRaised += ImportStatusUpdate;
|
||||
ImportContext.OnImportError.EventRaised += ImportError;
|
||||
|
||||
StorageLocation = await StorageLocationService.FirstAsync(l => l.Default && l.Type == StorageLocationType.Archive);
|
||||
}
|
||||
|
||||
private void RecordProcessed(object? sender, object e)
|
||||
private async Task ImportStarted(ImportStatusUpdate status)
|
||||
{
|
||||
_stage = ImportStage.Importing;
|
||||
_importProgress = -1;
|
||||
|
||||
await Task.Yield();
|
||||
await InvokeStateHasChangedAsync();
|
||||
}
|
||||
|
||||
private void RecordAdded(object? sender, object e)
|
||||
|
||||
private async Task ImportComplete(ImportStatusUpdate status)
|
||||
{
|
||||
_stage = ImportStage.Complete;
|
||||
_importProgress = 100;
|
||||
|
||||
await Clear();
|
||||
ImportContext.Dispose();
|
||||
|
||||
await Task.Yield();
|
||||
await InvokeStateHasChangedAsync();
|
||||
}
|
||||
|
||||
private async Task ImportStatusUpdate(ImportStatusUpdate status) => await UpdateProgress(status);
|
||||
|
||||
private void RecordError(object? sender, object e)
|
||||
private async Task ImportError(ImportStatusUpdate status)
|
||||
{
|
||||
|
||||
if (status.Error != null)
|
||||
_errors.Add(status.Error);
|
||||
|
||||
await Task.Yield();
|
||||
await InvokeStateHasChangedAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateProgress(ImportStatusUpdate? status)
|
||||
{
|
||||
if (status != null)
|
||||
{
|
||||
_importProgress = status.Total != 0 ? (int)Math.Floor((status.Index + 1) / (decimal)status.Total) : 100;
|
||||
|
||||
await Task.Yield();
|
||||
await InvokeStateHasChangedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
async Task Upload()
|
||||
|
|
@ -183,17 +226,13 @@ else if (_stage == ImportStage.Importing)
|
|||
await ImportContext.PrepareImportQueueAsync(selectedGuids, StorageLocation.Id);
|
||||
await ImportContext.ImportQueueAsync();
|
||||
|
||||
MessageService.Success("Game successfully imported!");
|
||||
MessageService.Success("Import started!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageService.Error("Game could not be imported!");
|
||||
MessageService.Error("Import failed!");
|
||||
Logger?.LogError(ex, "An unknown error occurred while trying to import");
|
||||
}
|
||||
|
||||
await Clear();
|
||||
ImportContext.Dispose();
|
||||
await CloseFeedbackAsync();
|
||||
}
|
||||
|
||||
async Task Clear()
|
||||
|
|
@ -235,7 +274,7 @@ else if (_stage == ImportStage.Importing)
|
|||
|
||||
ImportItems = await ImportContext.InitializeImportAsync(archivePath);
|
||||
|
||||
_selectedKeys = ImportItems.Select(i => i.Id.ToString()).ToArray();
|
||||
_selectedKeys = ImportItems.Select(i => i.Key).ToArray();
|
||||
|
||||
_stage = ImportStage.RecordSelection;
|
||||
|
||||
|
|
@ -277,7 +316,7 @@ else if (_stage == ImportStage.Importing)
|
|||
{
|
||||
ImportItems = await ImportContext.InitializeImportAsync(path);
|
||||
|
||||
_selectedKeys = ImportItems.Select(i => i.Id.ToString()).ToArray();
|
||||
_selectedKeys = ImportItems.Select(i => i.Key).ToArray();
|
||||
|
||||
_stage = ImportStage.RecordSelection;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue