Split uploader into separate component from table editor. Allow specification of object key on uploader for allowing replacement uploads.

dhcp-server
Pat Hartl 2023-08-28 18:00:22 -05:00
parent 1e98412b17
commit 2e4a31b136
7 changed files with 184 additions and 134 deletions

View File

@ -19,6 +19,8 @@ namespace LANCommander.Controllers
if (!System.IO.File.Exists(Path.Combine(UploadDirectory, key))) if (!System.IO.File.Exists(Path.Combine(UploadDirectory, key)))
System.IO.File.Create(Path.Combine(UploadDirectory, key)).Close(); System.IO.File.Create(Path.Combine(UploadDirectory, key)).Close();
else
System.IO.File.Delete(Path.Combine(UploadDirectory, key));
return Json(new return Json(new
{ {

View File

@ -0,0 +1,113 @@
@using System.Net;
@using System.Diagnostics;
@using Hangfire;
@using LANCommander.Jobs.Background;
@using Microsoft.EntityFrameworkCore;
@inject HttpClient HttpClient
@inject NavigationManager Navigator
@inject ArchiveService ArchiveService
@inject IMessageService MessageService
@inject IJSRuntime JS
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
<SpaceItem>
<Table TItem="Archive" DataSource="@Game.Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true">
<PropertyColumn Property="a => a.Version" />
<PropertyColumn Property="a => a.CompressedSize">
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
</PropertyColumn>
<PropertyColumn Property="a => a.CreatedBy">
@context.CreatedBy?.UserName
</PropertyColumn>
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" />
<ActionColumn Title="">
<Space Style="display: flex; justify-content: end">
<SpaceItem>
<Button OnClick="() => Download(context)" Icon="@IconType.Outline.Download" Type="@ButtonType.Text" />
</SpaceItem>
<SpaceItem>
<Popconfirm Title="Are you sure you want to delete this archive?" OnConfirm="() => Delete(context)">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
</SpaceItem>
</Space>
</ActionColumn>
</Table>
</SpaceItem>
<SpaceItem>
<GridRow Justify="end">
<GridCol>
<Button OnClick="UploadArchive" Type="@ButtonType.Primary">Upload Archive</Button>
</GridCol>
</GridRow>
</SpaceItem>
</Space>
<ArchiveUploader @ref="Uploader" OnArchiveUploaded="AddArchive" />
@code {
[Parameter] public Game Game { get; set; }
Archive Archive;
ArchiveUploader Uploader;
protected override async Task OnInitializedAsync()
{
await LoadData();
HttpClient.BaseAddress = new Uri(Navigator.BaseUri);
}
private async Task LoadData()
{
Game.Archives = await ArchiveService.Get(a => a.GameId == Game.Id).OrderByDescending(a => a.CreatedOn).ToListAsync();
if (Game.Archives == null)
Game.Archives = new List<Archive>();
}
private async Task Download(Archive archive)
{
string url = $"/Download/Game/{archive.Id}";
await JS.InvokeAsync<object>("open", url, "_blank");
}
private async Task UploadArchive()
{
Archive = new Archive()
{
GameId = Game.Id,
Id = Guid.NewGuid()
};
await Uploader.Open(Archive);
}
private async Task AddArchive(Archive archive)
{
var lastArchive = Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
Archive = await ArchiveService.Add(archive);
await LoadData();
if (lastArchive != null)
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(lastArchive.Id, Archive.Id));
}
private async Task Delete(Archive archive)
{
try
{
await ArchiveService.Delete(archive);
await MessageService.Success("Archive deleted!");
}
catch (Exception ex)
{
await MessageService.Error("Archive could not be deleted.");
}
}
}

View File

@ -8,41 +8,6 @@
@inject IMessageService MessageService @inject IMessageService MessageService
@inject IJSRuntime JS @inject IJSRuntime JS
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
<SpaceItem>
<Table TItem="Archive" DataSource="@Game.Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true">
<PropertyColumn Property="a => a.Version" />
<PropertyColumn Property="a => a.CompressedSize">
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
</PropertyColumn>
<PropertyColumn Property="a => a.CreatedBy">
@context.CreatedBy?.UserName
</PropertyColumn>
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" />
<ActionColumn Title="">
<Space Style="display: flex; justify-content: end">
<SpaceItem>
<Button OnClick="() => Download(context)" Icon="@IconType.Outline.Download" Type="@ButtonType.Text" />
</SpaceItem>
<SpaceItem>
<Popconfirm Title="Are you sure you want to delete this archive?" OnConfirm="() => Delete(context)">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
</SpaceItem>
</Space>
</ActionColumn>
</Table>
</SpaceItem>
<SpaceItem>
<GridRow Justify="end">
<GridCol>
<Button OnClick="AddArchive" Type="@ButtonType.Primary">Upload Archive</Button>
</GridCol>
</GridRow>
</SpaceItem>
</Space>
@{ @{
RenderFragment Footer = RenderFragment Footer =
@<Template> @<Template>
@ -52,7 +17,7 @@
</Template>; </Template>;
} }
<Modal Visible="@ModalVisible" Title="Upload Archive" OnOk="UploadArchiveJS" OnCancel="Cancel" Footer="@Footer"> <Modal Visible="@Visible" Title="Upload Archive" OnOk="UploadArchiveJS" OnCancel="Cancel" Footer="@Footer">
<Form Model="@Archive" Layout="@FormLayout.Vertical"> <Form Model="@Archive" Layout="@FormLayout.Vertical">
<FormItem Label="Version"> <FormItem Label="Version">
<Input @bind-Value="@context.Version" /> <Input @bind-Value="@context.Version" />
@ -97,7 +62,7 @@
</Modal> </Modal>
@code { @code {
[Parameter] public Game Game { get; set; } [Parameter] public EventCallback<Archive> OnArchiveUploaded { get; set; }
Archive Archive; Archive Archive;
@ -105,21 +70,13 @@
List<UploadFileItem> FileList = new List<UploadFileItem>(); List<UploadFileItem> FileList = new List<UploadFileItem>();
bool IsValid = false; bool IsValid = false;
bool ModalVisible = false; bool Visible = false;
private static string DefaultDragClass = "relative rounded-lg border-2 border-dashed pa-4 mt-4 mud-width-full mud-height-full z-10";
private string DragClass = DefaultDragClass;
const int ChunkSize = 1024 * 1024 * 10;
int Progress = 0; int Progress = 0;
bool Uploading = false; bool Uploading = false;
bool Finished = false; bool Finished = false;
double Speed = 0; double Speed = 0;
Stopwatch Watch;
long WatchBytesTransferred = 0;
string Filename; string Filename;
ProgressStatus CurrentProgressStatus { ProgressStatus CurrentProgressStatus {
@ -136,48 +93,7 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
if (Game.Archives == null)
Game.Archives = new List<Archive>();
HttpClient.BaseAddress = new Uri(Navigator.BaseUri); HttpClient.BaseAddress = new Uri(Navigator.BaseUri);
Archive = new Archive()
{
GameId = Game.Id,
Id = Guid.NewGuid()
};
}
private async Task Download(Archive archive)
{
string url = $"/Download/Game/{archive.Id}";
await JS.InvokeAsync<object>("open", url, "_blank");
}
private void AddArchive()
{
Archive = new Archive()
{
GameId = Game.Id,
Id = Guid.NewGuid()
};
ModalVisible = true;
}
private async Task Delete(Archive archive)
{
try
{
await ArchiveService.Delete(archive);
await MessageService.Success("Archive deleted!");
}
catch (Exception ex)
{
await MessageService.Error("Archive could not be deleted.");
}
} }
private void Clear() private void Clear()
@ -188,7 +104,7 @@
private void Cancel() private void Cancel()
{ {
File = null; File = null;
ModalVisible = false; Visible = false;
} }
private async void FileSelected(InputFileChangeEventArgs args) private async void FileSelected(InputFileChangeEventArgs args)
@ -196,13 +112,31 @@
File = args.File; File = args.File;
} }
public async Task Open(Archive archive)
{
Archive = archive;
Visible = true;
await InvokeAsync(StateHasChanged);
await Task.Delay(500);
if (!String.IsNullOrWhiteSpace(archive.ObjectKey) && archive.ObjectKey != Guid.Empty.ToString())
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", archive.ObjectKey.ToString());
else
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", "");
}
private async Task UploadArchiveJS() private async Task UploadArchiveJS()
{ {
Uploading = true; Uploading = true;
var dotNetReference = DotNetObjectReference.Create(this); var dotNetReference = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("Uploader.Upload", "FileInput", dotNetReference); await JS.InvokeVoidAsync("Uploader.Upload", dotNetReference);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }
@ -218,22 +152,18 @@
Archive.ObjectKey = objectKey.ToString(); Archive.ObjectKey = objectKey.ToString();
Archive.CompressedSize = File.Size; Archive.CompressedSize = File.Size;
var originalArchive = Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault(); Visible = false;
Archive = await ArchiveService.Add(Archive);
ModalVisible = false;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await MessageService.Success("Archive uploaded!"); if (OnArchiveUploaded.HasDelegate)
await OnArchiveUploaded.InvokeAsync(Archive);
if (originalArchive != null) await MessageService.Success("Archive uploaded!");
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(originalArchive.Id, Archive.Id));
} }
else else
{ {
ModalVisible = false; Visible = false;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

View File

@ -124,7 +124,7 @@
</div> </div>
<div data-panel="Archives"> <div data-panel="Archives">
<ArchiveUploader Game="Game" /> <ArchiveEditor Game="Game" />
</div> </div>
} }

View File

@ -23,29 +23,34 @@ export default class Uploader {
Key: string = ""; Key: string = "";
Id: string = ""; Id: string = "";
Init(fileInputId: string, uploadButtonId: string, objectKeyInputId: string) { async Init(fileInputId: string, objectKey: string) {
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
this.UploadButton = document.getElementById(uploadButtonId) as HTMLButtonElement;
this.ObjectKeyInput = document.getElementById(objectKeyInputId) as HTMLInputElement;
this.Chunks = [];
}
async Upload(fileInputId: string, dotNetObject: any) {
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement; this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
this.ProgressBar = document.querySelector('.uploader-progress .ant-progress-bg'); this.ProgressBar = document.querySelector('.uploader-progress .ant-progress-bg');
this.ProgressText = document.querySelector('.uploader-progress .ant-progress-text'); this.ProgressText = document.querySelector('.uploader-progress .ant-progress-text');
this.ProgressRate = document.querySelector('.uploader-progress-rate'); this.ProgressRate = document.querySelector('.uploader-progress-rate');
this.Chunks = [];
if (objectKey == undefined || objectKey == "") {
try {
var response = await axios.post<UploadInitResponse>(this.InitRoute);
this.Key = response.data.key;
}
catch (ex) {
this.Key = null;
console.error(`Could not init upload: ${ex}`);
}
}
else
this.Key = objectKey;
this.Chunks = [];
}
async Upload(dotNetObject: any) {
this.File = this.FileInput.files.item(0); this.File = this.FileInput.files.item(0);
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize); this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
try { try {
var resp = await axios.post<UploadInitResponse>(this.InitRoute);
this.Key = resp.data.key;
this.GetChunks(); this.GetChunks();
try { try {
@ -58,8 +63,7 @@ export default class Uploader {
this.OnError(); this.OnError();
} }
} catch (ex) { } catch (ex) {
this.Key = null; console.error(`Could not chunk upload: ${ex}`);
console.error(`Could not init upload: ${ex}`);
} finally { } finally {
dotNetObject.invokeMethodAsync('OnUploadComplete', this.Key); dotNetObject.invokeMethodAsync('OnUploadComplete', this.Key);
} }

View File

@ -57,24 +57,32 @@ class Uploader {
this.Key = ""; this.Key = "";
this.Id = ""; this.Id = "";
} }
Init(fileInputId, uploadButtonId, objectKeyInputId) { Init(fileInputId, objectKey) {
this.FileInput = document.getElementById(fileInputId);
this.UploadButton = document.getElementById(uploadButtonId);
this.ObjectKeyInput = document.getElementById(objectKeyInputId);
this.Chunks = [];
}
Upload(fileInputId, dotNetObject) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
this.FileInput = document.getElementById(fileInputId); this.FileInput = document.getElementById(fileInputId);
this.ProgressBar = document.querySelector('.uploader-progress .ant-progress-bg'); this.ProgressBar = document.querySelector('.uploader-progress .ant-progress-bg');
this.ProgressText = document.querySelector('.uploader-progress .ant-progress-text'); this.ProgressText = document.querySelector('.uploader-progress .ant-progress-text');
this.ProgressRate = document.querySelector('.uploader-progress-rate'); this.ProgressRate = document.querySelector('.uploader-progress-rate');
if (objectKey == undefined || objectKey == "") {
try {
var response = yield axios__WEBPACK_IMPORTED_MODULE_1__["default"].post(this.InitRoute);
this.Key = response.data.key;
}
catch (ex) {
this.Key = null;
console.error(`Could not init upload: ${ex}`);
}
}
else
this.Key = objectKey;
this.Chunks = []; this.Chunks = [];
});
}
Upload(dotNetObject) {
return __awaiter(this, void 0, void 0, function* () {
this.File = this.FileInput.files.item(0); this.File = this.FileInput.files.item(0);
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize); this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
try { try {
var resp = yield axios__WEBPACK_IMPORTED_MODULE_1__["default"].post(this.InitRoute);
this.Key = resp.data.key;
this.GetChunks(); this.GetChunks();
try { try {
for (let chunk of this.Chunks) { for (let chunk of this.Chunks) {
@ -87,8 +95,7 @@ class Uploader {
} }
} }
catch (ex) { catch (ex) {
this.Key = null; console.error(`Could not chunk upload: ${ex}`);
console.error(`Could not init upload: ${ex}`);
} }
finally { finally {
dotNetObject.invokeMethodAsync('OnUploadComplete', this.Key); dotNetObject.invokeMethodAsync('OnUploadComplete', this.Key);
@ -120,12 +127,6 @@ class Uploader {
console.error(ex); console.error(ex);
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`; throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
} }
finally {
console.log("Updating progress bar");
var percent = Math.ceil((chunk.Index / this.TotalChunks) * 100);
this.ProgressBar.style.width = percent + '%';
this.ProgressText.innerText = percent + '%';
}
}); });
} }
GetChunks() { GetChunks() {

File diff suppressed because one or more lines are too long