LANCommander/LANCommander.Server/UI/Components/ArchiveUploader.razor

249 lines
8.7 KiB
Text
Raw Permalink Normal View History

2026-05-28 22:24:46 -05:00
@using Hangfire;
@using LANCommander.SDK.Enums
2024-08-04 18:44:33 -05:00
@using LANCommander.Server.Jobs.Background;
2026-05-28 22:24:46 -05:00
@using LANCommander.UI.Services
@using Microsoft.Extensions.Options
2025-11-17 22:09:03 -06:00
@inject ArchiveService ArchiveService
2026-06-27 11:03:34 -05:00
@inject GameVersionService GameVersionService
@inject StorageLocationService StorageLocationService
@inject IOptions<Settings.Settings> Settings
@inject IMessageService MessageService
2026-05-28 22:24:46 -05:00
@inject ConfirmService ConfirmService
@inject IJSRuntime JS
2024-07-07 16:33:46 -05:00
@inject ILogger<ArchiveUploader> Logger
@{
RenderFragment Footer =
@<Template>
<Tooltip Title="Moves the selected local file instead of copy">
<Checkbox @bind-Checked="MoveLocalFile" Disabled="@(Status != "")">Move</Checkbox>
</Tooltip>
<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>;
}
2026-05-28 22:24:46 -05:00
<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">
2026-05-28 22:24:46 -05:00
<Input @bind-Value="@context.Version" />
</FormItem>
<FormItem Label="Path">
<StorageLocationSelector Type="StorageLocationType.Archive" @bind-Value="@context.StorageLocation" />
</FormItem>
<FormItem>
2026-05-28 22:24:46 -05:00
<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;
2025-01-21 20:43:28 -06:00
Guid StorageLocationId;
StorageLocation StorageLocation;
2026-05-28 22:24:46 -05:00
bool Visible = false;
bool Uploading = false;
string Filename;
2026-05-28 19:52:23 -05:00
string Status = "";
bool MoveLocalFile = false;
private async Task Start()
{
Uploading = true;
2026-05-28 22:24:46 -05:00
// 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;
await ChunkUploader.Start(async (string objectKeyStr) =>
{
2026-05-28 22:24:46 -05:00
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.CompressedSize = await ArchiveService.GetCompressedSizeAsync(uploadedArchive);
uploadedArchive.UncompressedSize = await ArchiveService.GetUncompressedSizeAsync(uploadedArchive);
await ArchiveService.UpdateAsync(uploadedArchive);
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()
{
2026-05-28 22:24:46 -05:00
if (ChunkUploader != null)
await ChunkUploader.Clear();
}
private async Task Cancel()
{
2026-05-28 22:24:46 -05:00
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)
{
2026-05-28 22:24:46 -05:00
Archive = await ArchiveService.GetAsync(archiveId.Value);
}
else
{
Archive = new Archive();
if (GameId != Guid.Empty)
{
Archive.GameId = GameId;
// Default the version to the game's current version so the upload is tagged
// against it unless the uploader chooses a different version number.
Archive.Version = await GameVersionService.GetLatestVersionNumberAsync(GameId) ?? string.Empty;
}
else if (RedistributableId != Guid.Empty)
Archive.RedistributableId = RedistributableId;
else if (ToolId != Guid.Empty)
Archive.ToolId = ToolId;
}
if (Archive.StorageLocation == null)
Archive.StorageLocation = await StorageLocationService.DefaultAsync(StorageLocationType.Archive) ?? new StorageLocation();
2026-05-28 22:24:46 -05:00
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!");
2024-07-07 16:33:46 -05:00
Logger.LogError($"Archive failed to upload: {message}");
}
public async Task OnLocalFileSelected(string path)
{
try
{
var objectKey = await ArchiveService.CopyFromLocalFileAsync(path, Archive.StorageLocation.Id, MoveLocalFile);
2026-05-28 22:24:46 -05:00
// 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 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.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");
2024-07-07 16:33:46 -05:00
Logger.LogError(ex, "An unknown error occurred while trying to use a local file");
}
}
}