Record archive file sizes. Automatically create archive record after upload.

dashboard
Pat Hartl 2023-01-06 02:06:19 -06:00
parent f5b136720a
commit 98bf631a9a
10 changed files with 765 additions and 41 deletions

View File

@ -0,0 +1,40 @@
using LANCommander.Data;
using LANCommander.Data.Models;
using LANCommander.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace LANCommander.Controllers.Api
{
[Authorize(AuthenticationSchemes = "Bearer")]
[Route("api/[controller]")]
[ApiController]
public class ArchivesController : ControllerBase
{
private DatabaseContext Context;
public ArchivesController(DatabaseContext context)
{
Context = context;
}
[HttpGet]
public async Task<IActionResult> Download(Guid id)
{
using (var repo = new Repository<Archive>(Context, HttpContext))
{
var archive = await repo.Find(id);
if (archive == null)
return NotFound();
var filename = Path.Combine("Upload", archive.ObjectKey);
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{archive.Game.Title.SanitizeFilename()}.zip");
}
}
}
}

View File

@ -103,34 +103,33 @@ namespace LANCommander.Controllers
return RedirectToAction("Edit", "Games", new { id = gameId });
}
public async Task<IActionResult> ValidateManifest(Guid id)
public async Task<IActionResult> Validate(Guid id, Archive archive)
{
var path = Path.Combine("Upload", id.ToString());
string manifestContents = String.Empty;
long compressedSize = 0;
long uncompressedSize = 0;
if (!System.IO.File.Exists(path))
return BadRequest("Specified object does not exist");
try
{
using (ZipArchive archive = ZipFile.OpenRead(path))
using (ZipArchive zip = ZipFile.OpenRead(path))
{
var manifest = archive.Entries.FirstOrDefault(e => e.FullName == "_manifest.yml");
var manifest = zip.Entries.FirstOrDefault(e => e.FullName == "_manifest.yml");
if (manifest == null)
throw new FileNotFoundException("Manifest file not found. Add a _manifest.yml file to your archive and try again.");
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName == "_manifest.yml")
{
using (StreamReader sr = new StreamReader(entry.Open()))
using (StreamReader sr = new StreamReader(manifest.Open()))
{
manifestContents = await sr.ReadToEndAsync();
}
}
}
compressedSize = zip.Entries.Sum(e => e.CompressedLength);
uncompressedSize = zip.Entries.Sum(e => e.Length);
}
}
catch (InvalidDataException ex)
@ -163,7 +162,32 @@ namespace LANCommander.Controllers
return BadRequest("The manifest file is invalid or corrupt.");
}
return Ok();
using (var repo = new Repository<Game>(Context, HttpContext))
{
var game = await repo.Find(archive.Game.Id);
if (game == null)
return BadRequest("The related game is missing or corrupt.");
archive.Game = game;
}
using (var repo = new Repository<Archive>(Context, HttpContext))
{
archive.Id = Guid.Empty;
archive.CompressedSize = compressedSize;
archive.UncompressedSize = uncompressedSize;
archive.ObjectKey = id.ToString();
archive = await repo.Add(archive);
await repo.SaveChanges();
return Json(new
{
Id = archive.Id,
ObjectKey = archive.ObjectKey,
});
}
}
}
}

View File

@ -15,5 +15,8 @@ namespace LANCommander.Data.Models
public virtual Game Game { get; set; }
public virtual Archive? LastVersion { get; set; }
public long UncompressedSize { get; set; }
public long CompressedSize { get; set; }
}
}

View File

@ -0,0 +1,572 @@
// <auto-generated />
using System;
using LANCommander.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace LANCommander.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230106065219_AddArchiveFileSizes")]
partial class AddArchiveFileSizes
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
modelBuilder.Entity("GameTag", b =>
{
b.Property<Guid>("GamesId")
.HasColumnType("TEXT");
b.Property<Guid>("TagsId")
.HasColumnType("TEXT");
b.HasKey("GamesId", "TagsId");
b.HasIndex("TagsId");
b.ToTable("GameTag");
});
modelBuilder.Entity("LANCommander.Data.Models.Archive", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Changelog")
.HasColumnType("TEXT");
b.Property<long>("CompressedSize")
.HasColumnType("INTEGER");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.Property<Guid?>("LastVersionId")
.HasColumnType("TEXT");
b.Property<string>("ObjectKey")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("UncompressedSize")
.HasColumnType("INTEGER");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("GameId");
b.HasIndex("LastVersionId");
b.HasIndex("UpdatedById");
b.ToTable("Archive");
});
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("UpdatedById");
b.ToTable("Companies");
});
modelBuilder.Entity("LANCommander.Data.Models.Game", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid?>("DeveloperId")
.HasColumnType("TEXT");
b.Property<string>("DirectoryName")
.HasColumnType("TEXT");
b.Property<Guid?>("PublisherId")
.HasColumnType("TEXT");
b.Property<DateTime>("ReleasedOn")
.HasColumnType("TEXT");
b.Property<string>("SortTitle")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("DeveloperId");
b.HasIndex("PublisherId");
b.HasIndex("UpdatedById");
b.ToTable("Games");
});
modelBuilder.Entity("LANCommander.Data.Models.Role", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("UpdatedById");
b.ToTable("Tags");
});
modelBuilder.Entity("LANCommander.Data.Models.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("RefreshToken")
.HasColumnType("TEXT");
b.Property<DateTime>("RefreshTokenExpiration")
.HasColumnType("TEXT");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<Guid>("RoleId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<Guid>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("GameTag", b =>
{
b.HasOne("LANCommander.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Data.Models.Tag", null)
.WithMany()
.HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LANCommander.Data.Models.Archive", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Archives")
.HasForeignKey("GameId");
b.HasOne("LANCommander.Data.Models.Archive", "LastVersion")
.WithMany()
.HasForeignKey("LastVersionId");
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("CreatedBy");
b.Navigation("Game");
b.Navigation("LastVersion");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("CreatedBy");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Game", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
b.HasOne("LANCommander.Data.Models.Company", "Developer")
.WithMany("DevelopedGames")
.HasForeignKey("DeveloperId");
b.HasOne("LANCommander.Data.Models.Company", "Publisher")
.WithMany("PublishedGames")
.HasForeignKey("PublisherId");
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("CreatedBy");
b.Navigation("Developer");
b.Navigation("Publisher");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("CreatedBy");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("LANCommander.Data.Models.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("LANCommander.Data.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("LANCommander.Data.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("LANCommander.Data.Models.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Data.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("LANCommander.Data.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
{
b.Navigation("DevelopedGames");
b.Navigation("PublishedGames");
});
modelBuilder.Entity("LANCommander.Data.Models.Game", b =>
{
b.Navigation("Archives");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
public partial class AddArchiveFileSizes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "CompressedSize",
table: "Archive",
type: "INTEGER",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "UncompressedSize",
table: "Archive",
type: "INTEGER",
nullable: false,
defaultValue: 0L);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CompressedSize",
table: "Archive");
migrationBuilder.DropColumn(
name: "UncompressedSize",
table: "Archive");
}
}
}

View File

@ -41,6 +41,9 @@ namespace LANCommander.Migrations
b.Property<string>("Changelog")
.HasColumnType("TEXT");
b.Property<long>("CompressedSize")
.HasColumnType("INTEGER");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
@ -57,6 +60,9 @@ namespace LANCommander.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("UncompressedSize")
.HasColumnType("INTEGER");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");

View File

@ -53,6 +53,8 @@
</div>
</div>
<input type="hidden" asp-for="Game.Id" />
<input type="hidden" asp-for="LastVersion.Id" />
<input type="hidden" asp-for="ObjectKey" />
</div>
</div>
@ -62,7 +64,6 @@
<div class="d-flex">
<a asp-action="Edit" asp-controller="Games" asp-route-id="@Model.Game.Id" class="btn btn-link">Cancel</a>
<button class="btn btn-primary" id="UploadButton">Upload</button>
<input type="submit" value="Save" id="SaveButton" class="btn btn-primary" style="display: none;" />
</div>
</div>
</fieldset>
@ -90,14 +91,11 @@
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.text('0%');
$('#UploadButton').show();
$('#SaveButton').hide();
};
uploader.OnComplete = (key) => {
uploader.OnComplete = (id, key) => {
$('#Id').val(id);
$('#ObjectKey').val(key);
$('fieldset').prop('disabled', false);
$('.progress-bar')
.css('width', '100%')
.removeClass('progress-bar-striped')
@ -105,8 +103,9 @@
.addClass('bg-success')
.text('Upload Complete!');
$('#UploadButton').hide();
$('#SaveButton').show();
setTimeout(() => {
window.location.href = '@Url.Action("Edit", "Games", new { id = Model.Game.Id })';
}, 2000);
};
uploader.OnProgress = (percent) => {
@ -123,9 +122,6 @@
.removeClass('progress-bar-animated')
.addClass('bg-danger')
.text('Upload Error!');
$('#UploadButton').show();
$('#SaveButton').hide();
};
</script>
}

View File

@ -18,12 +18,16 @@ class Uploader {
constructor() {
this.InitRoute = "/Upload/Init";
this.ChunkRoute = "/Upload/Chunk";
this.ValidateManifestRoute = "/Archives/ValidateManifest";
this.ValidateRoute = "/Archives/Validate";
this.MaxChunkSize = 1024 * 1024 * 25;
}
Init(fileInputId, uploadButtonId) {
this.FileInput = document.getElementById(fileInputId);
this.UploadButton = document.getElementById(uploadButtonId);
this.FileInput = document.getElementById("File");
this.UploadButton = document.getElementById("UploadButton");
this.VersionInput = document.getElementById("Version");
this.ChangelogTextArea = document.getElementById("Changelog");
this.LastVersionIdInput = document.getElementById("LastVersion_Id");
this.GameIdInput = document.getElementById("Game_Id");
this.ParentForm = this.FileInput.closest("form");
this.Chunks = [];
this.UploadButton.onclick = (e) => __awaiter(this, void 0, void 0, function* () {
@ -47,9 +51,9 @@ class Uploader {
for (let chunk of this.Chunks) {
yield this.UploadChunk(chunk);
}
var isValid = yield this.ValidateManifest();
var isValid = yield this.Validate();
if (isValid)
this.OnComplete(this.Key);
this.OnComplete(this.Id, this.Key);
else
this.OnError();
}
@ -77,15 +81,27 @@ class Uploader {
this.OnProgress(chunk.Index / this.TotalChunks);
});
}
ValidateManifest() {
Validate() {
return __awaiter(this, void 0, void 0, function* () {
let validationResponse = yield fetch(`${this.ValidateManifestRoute}/${this.Key}`, {
method: "GET"
let formData = new FormData();
formData.append('Version', this.VersionInput.value);
formData.append('Changelog', this.ChangelogTextArea.value);
formData.append('Game.Id', this.GameIdInput.value);
formData.append('ObjectKey', this.Key);
let validationResponse = yield fetch(`${this.ValidateRoute}/${this.Key}`, {
method: "POST",
body: formData
});
if (!validationResponse.ok) {
ErrorModal.Show("Archive Invalid", yield validationResponse.text());
return false;
}
let data = yield validationResponse.json();
if (data == null || data.Id === "") {
ErrorModal.Show("Upload Error", "Something interfered with the upload. Try again.");
return false;
}
this.Id = data.Id;
return true;
});
}

View File

@ -1 +1 @@
{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"}

View File

@ -14,11 +14,18 @@ class Uploader {
ParentForm: HTMLFormElement;
FileInput: HTMLInputElement;
UploadButton: HTMLButtonElement;
VersionInput: HTMLInputElement;
LastVersionIdInput: HTMLInputElement;
GameIdInput: HTMLInputElement;
ChangelogTextArea: HTMLTextAreaElement;
ObjectKeyInput: HTMLInputElement;
IdInput: HTMLInputElement;
File: File;
InitRoute: string = "/Upload/Init";
ChunkRoute: string = "/Upload/Chunk";
ValidateManifestRoute: string = "/Archives/ValidateManifest";
ValidateRoute: string = "/Archives/Validate";
MaxChunkSize: number = 1024 * 1024 * 25;
TotalChunks: number;
@ -26,10 +33,15 @@ class Uploader {
Chunks: Chunk[];
Key: string;
Id: string;
Init(fileInputId: string, uploadButtonId: string) {
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
this.UploadButton = document.getElementById(uploadButtonId) as HTMLButtonElement;
this.FileInput = document.getElementById("File") as HTMLInputElement;
this.UploadButton = document.getElementById("UploadButton") as HTMLButtonElement;
this.VersionInput = document.getElementById("Version") as HTMLInputElement;
this.ChangelogTextArea = document.getElementById("Changelog") as HTMLTextAreaElement;
this.LastVersionIdInput = document.getElementById("LastVersion_Id") as HTMLInputElement;
this.GameIdInput = document.getElementById("Game_Id") as HTMLInputElement;
this.ParentForm = this.FileInput.closest("form");
this.Chunks = [];
@ -63,10 +75,10 @@ class Uploader {
await this.UploadChunk(chunk);
}
var isValid = await this.ValidateManifest();
var isValid = await this.Validate();
if (isValid)
this.OnComplete(this.Key);
this.OnComplete(this.Id, this.Key);
else
this.OnError();
}
@ -98,9 +110,17 @@ class Uploader {
this.OnProgress(chunk.Index / this.TotalChunks);
}
async ValidateManifest(): Promise<boolean> {
let validationResponse = await fetch(`${this.ValidateManifestRoute}/${this.Key}`, {
method: "GET"
async Validate(): Promise<boolean> {
let formData = new FormData();
formData.append('Version', this.VersionInput.value);
formData.append('Changelog', this.ChangelogTextArea.value);
formData.append('Game.Id', this.GameIdInput.value);
formData.append('ObjectKey', this.Key);
let validationResponse = await fetch(`${this.ValidateRoute}/${this.Key}`, {
method: "POST",
body: formData
});
if (!validationResponse.ok) {
@ -109,6 +129,16 @@ class Uploader {
return false;
}
let data = await validationResponse.json();
if (data == null || data.Id === "") {
ErrorModal.Show("Upload Error", "Something interfered with the upload. Try again.");
return false;
}
this.Id = data.Id;
return true;
}
@ -125,7 +155,7 @@ class Uploader {
}
OnStart: () => void;
OnComplete: (key: string) => void;
OnComplete: (id: string, key: string) => void;
OnProgress: (percent: number) => void;
OnError: () => void;
}