256 lines
9.2 KiB
Text
256 lines
9.2 KiB
Text
@using Hangfire;
|
|
@using LANCommander.SDK.Enums
|
|
@using LANCommander.Server.Jobs.Background;
|
|
@using LANCommander.UI.Services
|
|
@using Microsoft.Extensions.Options
|
|
@inject ArchiveService ArchiveService
|
|
@inject IOptions<Settings.Settings> Settings
|
|
@inject IMessageService MessageService
|
|
@inject ConfirmService ConfirmService
|
|
@inject IJSRuntime JS
|
|
@inject ILogger<ArchiveUploader> Logger
|
|
|
|
@{
|
|
RenderFragment Footer =
|
|
@<Template>
|
|
<FilePickerButton EntrySelectable="@(entry => !String.IsNullOrWhiteSpace(entry.Name) && entry.Name.ToLower().EndsWith(".zip"))"
|
|
OnSelected="OnLocalFileSelected"
|
|
Root="@RootPath"
|
|
Disabled="@(Status != "" || String.IsNullOrWhiteSpace(Archive.Version))">
|
|
Use Local File
|
|
</FilePickerButton>
|
|
<Button OnClick="Start" Disabled="@(File == null || Status != "" || String.IsNullOrWhiteSpace(Archive.Version))" Type="@ButtonType.Primary">Upload</Button>
|
|
<Button OnClick="Cancel">Cancel</Button>
|
|
</Template>;
|
|
}
|
|
|
|
<Modal Visible="@Visible" Title="Upload Archive" OnOk="Start" OnCancel="Cancel" Footer="@Footer" Closable="true" MaskClosable="false">
|
|
<Form Model="@Archive" Layout="@FormLayout.Vertical">
|
|
<FormItem Label="Version">
|
|
<Input @bind-Value="@context.Version" />
|
|
</FormItem>
|
|
|
|
<FormItem Label="Changelog">
|
|
<TextArea @bind-Value="@context.Changelog" MaxLength=500 ShowCount />
|
|
</FormItem>
|
|
|
|
<FormItem Label="Path">
|
|
<StorageLocationSelector Type="StorageLocationType.Archive" @bind-Value="@context.StorageLocation" />
|
|
</FormItem>
|
|
|
|
<FormItem>
|
|
<ChunkUploader @ref="ChunkUploader" Accept=".zip" @bind-File="File" @bind-Status="Status" @bind-StorageLocationId="@(context.StorageLocation.Id)">
|
|
<Text>
|
|
<p>Drag and Drop</p>
|
|
<p>or</p>
|
|
<p>
|
|
<Button Type="@ButtonType.Primary" Style="margin-top: 8px;">Browse</Button>
|
|
</p>
|
|
</Text>
|
|
<Hint>Only ZIP files are supported as game archives</Hint>
|
|
</ChunkUploader>
|
|
</FormItem>
|
|
</Form>
|
|
</Modal>
|
|
|
|
@code {
|
|
[Parameter] public Guid GameId { get; set; }
|
|
[Parameter] public Guid RedistributableId { get; set; }
|
|
[Parameter] public Guid ToolId { get; set; }
|
|
[Parameter] public EventCallback<Guid> OnArchiveUploaded { get; set; }
|
|
|
|
Archive Archive;
|
|
|
|
string RootPath = Path.GetPathRoot(Directory.GetCurrentDirectory());
|
|
|
|
IBrowserFile File;
|
|
ChunkUploader ChunkUploader;
|
|
|
|
Guid StorageLocationId;
|
|
StorageLocation StorageLocation;
|
|
|
|
bool Visible = false;
|
|
bool Uploading = false;
|
|
|
|
string Filename;
|
|
|
|
string Status = "";
|
|
|
|
private async Task Start()
|
|
{
|
|
Uploading = true;
|
|
|
|
// Capture values NOW (at the moment user clicks Upload)
|
|
// so the callback has correct data regardless of component lifecycle.
|
|
var gameId = Archive.GameId;
|
|
var redistributableId = Archive.RedistributableId;
|
|
var toolId = Archive.ToolId;
|
|
var version = Archive.Version;
|
|
var changelog = Archive.Changelog;
|
|
|
|
await ChunkUploader.Start(async (string objectKeyStr) =>
|
|
{
|
|
try
|
|
{
|
|
if (!Guid.TryParse(objectKeyStr, out var objectKey))
|
|
{
|
|
Logger.LogError("Archive failed to upload - invalid object key");
|
|
return;
|
|
}
|
|
|
|
var uploadedArchive = await ArchiveService.FirstOrDefaultAsync(a => a.ObjectKey == objectKey.ToString());
|
|
|
|
if (uploadedArchive == null)
|
|
{
|
|
Logger.LogError("Archive record not found for object key {ObjectKey}", objectKeyStr);
|
|
return;
|
|
}
|
|
|
|
uploadedArchive.GameId = gameId;
|
|
uploadedArchive.RedistributableId = redistributableId;
|
|
uploadedArchive.ToolId = toolId;
|
|
uploadedArchive.Version = version;
|
|
uploadedArchive.Changelog = changelog;
|
|
uploadedArchive.CompressedSize = await ArchiveService.GetCompressedSizeAsync(uploadedArchive);
|
|
uploadedArchive.UncompressedSize = await ArchiveService.GetUncompressedSizeAsync(uploadedArchive);
|
|
|
|
await ArchiveService.UpdateAsync(uploadedArchive);
|
|
|
|
if (Settings.Value.Server.Archives.EnablePatching)
|
|
{
|
|
Archive? lastArchive = null;
|
|
|
|
if (gameId != Guid.Empty)
|
|
lastArchive = await ArchiveService.GetLatestArchiveAsync(a => a.Id != uploadedArchive.Id && a.GameId == gameId);
|
|
else if (redistributableId != Guid.Empty)
|
|
lastArchive = await ArchiveService.GetLatestArchiveAsync(a => a.Id != uploadedArchive.Id && a.RedistributableId == redistributableId);
|
|
else if (toolId != Guid.Empty)
|
|
lastArchive = await ArchiveService.GetLatestArchiveAsync(a => a.Id != uploadedArchive.Id && a.ToolId == toolId);
|
|
|
|
if (lastArchive != null)
|
|
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(lastArchive.Id, uploadedArchive.Id));
|
|
}
|
|
|
|
if (OnArchiveUploaded.HasDelegate)
|
|
await OnArchiveUploaded.InvokeAsync(uploadedArchive.Id);
|
|
|
|
Uploading = false;
|
|
Visible = false;
|
|
|
|
try { await InvokeAsync(StateHasChanged); } catch { }
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Failed to process uploaded archive");
|
|
}
|
|
});
|
|
}
|
|
|
|
private async Task Clear()
|
|
{
|
|
if (ChunkUploader != null)
|
|
await ChunkUploader.Clear();
|
|
}
|
|
|
|
private async Task Cancel()
|
|
{
|
|
if (Uploading && ChunkUploader?.IsUploading == true)
|
|
{
|
|
var result = await ConfirmService.Show(
|
|
"The upload will continue in the background. You can track its progress in the sidebar.",
|
|
"Continue in Background?",
|
|
ConfirmButtons.OKCancel,
|
|
ConfirmIcon.Info);
|
|
|
|
if (result == ConfirmResult.OK)
|
|
{
|
|
Visible = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ChunkUploader != null)
|
|
await ChunkUploader.Clear();
|
|
|
|
Visible = false;
|
|
}
|
|
}
|
|
|
|
public async Task Open(Guid? archiveId = null)
|
|
{
|
|
if (archiveId.HasValue && archiveId != Guid.Empty)
|
|
{
|
|
Archive = await ArchiveService.GetAsync(archiveId.Value);
|
|
}
|
|
else
|
|
{
|
|
Archive = new Archive();
|
|
|
|
if (GameId != Guid.Empty)
|
|
Archive.GameId = GameId;
|
|
else if (RedistributableId != Guid.Empty)
|
|
Archive.RedistributableId = RedistributableId;
|
|
else if (ToolId != Guid.Empty)
|
|
Archive.ToolId = ToolId;
|
|
}
|
|
|
|
Uploading = false;
|
|
Visible = true;
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
public async Task OnUploadError(string message)
|
|
{
|
|
Uploading = false;
|
|
Visible = false;
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
MessageService.Error("Archive failed to upload!");
|
|
Logger.LogError($"Archive failed to upload: {message}");
|
|
}
|
|
|
|
public async Task OnLocalFileSelected(string path)
|
|
{
|
|
try
|
|
{
|
|
var objectKey = await ArchiveService.CopyFromLocalFileAsync(path, Archive.StorageLocation.Id);
|
|
|
|
// For local files, run the completion logic directly.
|
|
var gameId = Archive.GameId;
|
|
var redistributableId = Archive.RedistributableId;
|
|
var toolId = Archive.ToolId;
|
|
var version = Archive.Version;
|
|
var changelog = Archive.Changelog;
|
|
|
|
var uploadedArchive = await ArchiveService.FirstOrDefaultAsync(a => a.ObjectKey == objectKey.ToString());
|
|
|
|
if (uploadedArchive != null)
|
|
{
|
|
uploadedArchive.GameId = gameId;
|
|
uploadedArchive.RedistributableId = redistributableId;
|
|
uploadedArchive.ToolId = toolId;
|
|
uploadedArchive.Version = version;
|
|
uploadedArchive.Changelog = changelog;
|
|
uploadedArchive.CompressedSize = await ArchiveService.GetCompressedSizeAsync(uploadedArchive);
|
|
uploadedArchive.UncompressedSize = await ArchiveService.GetUncompressedSizeAsync(uploadedArchive);
|
|
|
|
await ArchiveService.UpdateAsync(uploadedArchive);
|
|
|
|
if (OnArchiveUploaded.HasDelegate)
|
|
await OnArchiveUploaded.InvokeAsync(uploadedArchive.Id);
|
|
}
|
|
|
|
Visible = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
MessageService.Success("Archive uploaded!");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageService.Error("An unknown error occurred while trying to use a local file");
|
|
Logger.LogError(ex, "An unknown error occurred while trying to use a local file");
|
|
}
|
|
}
|
|
}
|