Skip to content

Commit

Permalink
Fancy build list page.
Browse files Browse the repository at this point in the history
  • Loading branch information
PJB3005 committed Jul 12, 2024
1 parent 3847aa0 commit 318205a
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 50 deletions.
4 changes: 4 additions & 0 deletions Robust.Cdn/Config/ManifestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public sealed class ManifestForkOptions
/// This is seen as acceptable as those generally don't take too much space.
/// </remarks>
public int PruneBuildsDays { get; set; } = 90;

public string? DisplayName { get; set; }
public string? BuildsPageLink { get; set; }
public string? BuildsPageLinkText { get; set; }
}

public sealed class ManifestForkNotifyWatchdog
Expand Down
99 changes: 99 additions & 0 deletions Robust.Cdn/Controllers/ForkBuildPageController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Diagnostics.CodeAnalysis;
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Robust.Cdn.Config;

namespace Robust.Cdn.Controllers;

[Controller]
[Route("/fork/{fork}")]
public sealed class ForkBuildPageController(
ManifestDatabase database,
IOptions<ManifestOptions> manifestOptions)
: Controller
{
[HttpGet]
public IActionResult Index(string fork)
{
if (!TryCheckBasicAuth(fork, out var errorResult))
return errorResult;

var versions = new List<Version>();

using var tx = database.Connection.BeginTransaction();

var dbVersions = database.Connection.Query<DbVersion>(
"""
SELECT FV.Id, FV.Name, PublishedTime, EngineVersion
FROM ForkVersion FV
INNER JOIN main.Fork F ON FV.ForkId = F.Id
WHERE F.Name = @Fork
AND FV.Available
ORDER BY PublishedTime DESC
LIMIT 50
""", new { Fork = fork });

foreach (var dbVersion in dbVersions)
{
var servers = database.Connection.Query<VersionServer>("""
SELECT Platform, FileName, FileSize
FROM ForkVersionServerBuild
WHERE ForkVersionId = @ForkVersionId
ORDER BY Platform
""", new { ForkVersionId = dbVersion.Id });

versions.Add(new Version
{
Name = dbVersion.Name,
EngineVersion = dbVersion.EngineVersion,
PublishedTime = DateTime.SpecifyKind(dbVersion.PublishedTime, DateTimeKind.Utc),
Servers = servers.ToArray()
});
}

return View(new Model
{
Fork = fork,
Options = manifestOptions.Value.Forks[fork],
Versions = versions
});
}

private bool TryCheckBasicAuth(
string fork,
[NotNullWhen(false)] out IActionResult? errorResult)
{
return ForkManifestController.TryCheckBasicAuth(HttpContext, manifestOptions.Value, fork, out errorResult);
}

public sealed class Model
{
public required string Fork;
public required ManifestForkOptions Options;
public required List<Version> Versions;
}

public sealed class Version
{
public required string Name;
public required DateTime PublishedTime;
public required string EngineVersion;
public required VersionServer[] Servers;
}

public sealed class VersionServer
{
public required string Platform { get; set; }
public required string FileName { get; set; }
public required long? FileSize { get; set; }
}

private sealed class DbVersion
{
public required int Id { get; set; }
public required string Name { get; set; }
public required DateTime PublishedTime { get; set; }
public required string EngineVersion { get; set; }
}
}
56 changes: 20 additions & 36 deletions Robust.Cdn/Controllers/ForkManifestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public sealed class ForkManifestController(
: ControllerBase
{
[HttpGet("manifest")]
public IActionResult GetManifest([FromHeader(Name = "Authorization")] string? authorization, string fork)
public IActionResult GetManifest(string fork)
{
if (!TryCheckBasicAuth(authorization, fork, out var errorResult))
if (!TryCheckBasicAuth(fork, out var errorResult))
return errorResult;

var rowId = database.Connection.QuerySingleOrDefault<long>(
Expand All @@ -46,7 +46,6 @@ public IActionResult GetManifest([FromHeader(Name = "Authorization")] string? au

[HttpGet("version/{version}/file/{file}")]
public IActionResult GetFile(
[FromHeader(Name = "Authorization")] string? authorization,
string fork,
string version,
string file)
Expand All @@ -55,7 +54,7 @@ public IActionResult GetFile(
if (file.Contains('/') || file == ".." || file == ".")
return BadRequest();

if (!TryCheckBasicAuth(authorization, fork, out var errorResult))
if (!TryCheckBasicAuth(fork, out var errorResult))
return errorResult;

var versionExists = database.Connection.QuerySingleOrDefault<bool>("""
Expand All @@ -75,13 +74,21 @@ SELECT 1
}

private bool TryCheckBasicAuth(
string? authorization,
string fork,
[NotNullWhen(false)] out IActionResult? errorResult)
{
if (!manifestOptions.Value.Forks.TryGetValue(fork, out var forkConfig))
return TryCheckBasicAuth(HttpContext, manifestOptions.Value, fork, out errorResult);
}

internal static bool TryCheckBasicAuth(
HttpContext httpContext,
ManifestOptions manifestOptions,
string fork,
[NotNullWhen(false)] out IActionResult? errorResult)
{
if (!manifestOptions.Forks.TryGetValue(fork, out var forkConfig))
{
errorResult = NotFound("Fork does not exist");
errorResult = new NotFoundObjectResult("Fork does not exist");
return false;
}

Expand All @@ -91,34 +98,11 @@ private bool TryCheckBasicAuth(
return true;
}

if (authorization == null)
{
errorResult = new UnauthorizedResult();
return false;
}

if (!AuthorizationUtility.TryParseBasicAuthentication(
authorization,
out errorResult,
out var userName,
out var password))
{
return false;
}

if (!forkConfig.PrivateUsers.TryGetValue(userName, out var expectedPassword))
{
errorResult = new UnauthorizedResult();
return false;
}

if (!AuthorizationUtility.BasicAuthMatches(password, expectedPassword))
{
errorResult = new UnauthorizedResult();
return false;
}

errorResult = null;
return true;
return AuthorizationUtility.CheckBasicAuth(
httpContext,
$"fork_{fork}",
a => forkConfig.PrivateUsers.GetValueOrDefault(a),
out _,
out errorResult);
}
}
16 changes: 8 additions & 8 deletions Robust.Cdn/Controllers/ForkPublishController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ namespace Robust.Cdn.Controllers;
public sealed partial class ForkPublishController(
ForkAuthHelper authHelper,
IHttpClientFactory httpFactory,
IOptions<ManifestOptions> manifestOptions,
ManifestDatabase manifestDatabase,
ISchedulerFactory schedulerFactory,
BaseUrlManager baseUrlManager,
Expand Down Expand Up @@ -311,7 +310,7 @@ private void AddVersionToDatabase(

var forkId = dbCon.QuerySingle<int>("SELECT Id FROM Fork WHERE Name = @Name", new { Name = fork });

var (clientName, clientSha256) = GetFileNameSha256Pair(diskFiles[clientArtifact]);
var (clientName, clientSha256, _) = GetFileNameSha256Pair(diskFiles[clientArtifact]);

var versionId = dbCon.QuerySingle<int>("""
INSERT INTO ForkVersion (Name, ForkId, PublishedTime, ClientFileName, ClientSha256, EngineVersion)
Expand All @@ -333,29 +332,30 @@ RETURNING Id
if (artifact.Type != ArtifactType.Server)
continue;

var (serverName, serverSha256) = GetFileNameSha256Pair(diskPath);
var (serverName, serverSha256, fileSize) = GetFileNameSha256Pair(diskPath);

dbCon.Execute("""
INSERT INTO ForkVersionServerBuild (ForkVersionId, Platform, FileName, Sha256)
VALUES (@ForkVersion, @Platform, @ServerName, @ServerSha256)
INSERT INTO ForkVersionServerBuild (ForkVersionId, Platform, FileName, Sha256, FileSize)
VALUES (@ForkVersion, @Platform, @ServerName, @ServerSha256, @FileSize)
""",
new
{
ForkVersion = versionId,
artifact.Platform,
ServerName = serverName,
ServerSha256 = serverSha256
ServerSha256 = serverSha256,
FileSize = fileSize
});
}

tx.Commit();
}

private static (string name, byte[] hash) GetFileNameSha256Pair(string diskPath)
private static (string name, byte[] hash, long size) GetFileNameSha256Pair(string diskPath)
{
using var file = System.IO.File.OpenRead(diskPath);

return (Path.GetFileName(diskPath), SHA256.HashData(file));
return (Path.GetFileName(diskPath), SHA256.HashData(file), file.Length);
}

private async Task QueueIngestJobAsync(string fork)
Expand Down
50 changes: 50 additions & 0 deletions Robust.Cdn/Helpers/AuthorizationUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,54 @@ public static bool BasicAuthMatches(string provided, string expected)
Encoding.UTF8.GetBytes(provided),
Encoding.UTF8.GetBytes(expected));
}

public static bool CheckBasicAuth(
HttpContext httpContext,
string realm,
Func<string, string?> getPassword,
[NotNullWhen(true)] out string? user,
[NotNullWhen(false)] out IActionResult? failure)
{
user = null;

if (!httpContext.Request.Headers.TryGetValue("Authorization", out var authValues))
{
SetWwwAuthenticate(httpContext, realm);
failure = new UnauthorizedResult();
return false;
}

var authValue = authValues[0]!;
if (!TryParseBasicAuthentication(
authValue,
out failure,
out user,
out var password))
{
SetWwwAuthenticate(httpContext, realm);
return false;
}

var expectedPassword = getPassword(user);
if (expectedPassword == null)
{
SetWwwAuthenticate(httpContext, realm);
failure = new UnauthorizedResult();
return false;
}

if (!BasicAuthMatches(password, expectedPassword))
{
SetWwwAuthenticate(httpContext, realm);
failure = new UnauthorizedResult();
return false;
}

return true;
}

private static void SetWwwAuthenticate(HttpContext context, string realm)
{
context.Response.Headers.WWWAuthenticate = $"Basic realm={realm}";
}
}
29 changes: 29 additions & 0 deletions Robust.Cdn/Helpers/ByteHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Robust.Cdn.Helpers;

public static class ByteHelpers
{
public static string FormatBytes(long bytes)
{
double d = bytes;
var i = 0;
for (; i < ByteSuffixes.Length && d >= 1024; i++)
{
d /= 1024;
}

return $"{Math.Round(d, 2)} {ByteSuffixes[i]}";
}

private static readonly string[] ByteSuffixes =
[
"B",
"KiB",
"MiB",
"GiB",
"TiB",
"PiB",
"EiB",
"ZiB",
"YiB"
];
}
12 changes: 8 additions & 4 deletions Robust.Cdn/Jobs/UpdateForkManifestJob.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Dapper;
using Quartz;
using Robust.Cdn.Helpers;
Expand Down Expand Up @@ -87,18 +88,19 @@ FROM ForkVersion
Server = new Dictionary<string, ManifestArtifact>()
};

var servers = database.Connection.Query<(string platform, string fileName, byte[] sha256)>("""
SELECT Platform, FileName, Sha256
var servers = database.Connection.Query<(string platform, string fileName, byte[] sha256, long? size)>("""
SELECT Platform, FileName, Sha256, FileSize
FROM ForkVersionServerBuild
WHERE ForkVersionId = @ForkVersionId
""", new { ForkVersionId = version.id });

foreach (var (platform, fileName, sha256) in servers)
foreach (var (platform, fileName, sha256, size) in servers)
{
buildData.Server.Add(platform, new ManifestArtifact
{
Url = baseUrlManager.MakeBuildInfoUrl($"fork/{fork}/version/{version.name}/file/{fileName}"),
Sha256 = Convert.ToHexString(sha256)
Sha256 = Convert.ToHexString(sha256),
Size = size
});
}

Expand Down Expand Up @@ -132,5 +134,7 @@ private sealed class ManifestArtifact
{
public required string Url { get; set; }
public required string Sha256 { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public long? Size { get; set; }
}
}
1 change: 1 addition & 0 deletions Robust.Cdn/ManifestMigrations/Script0002_AddBuildSize.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE ForkVersionServerBuild ADD COLUMN FileSize INTEGER NULL;
2 changes: 1 addition & 1 deletion Robust.Cdn/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
builder.Services.Configure<CdnOptions>(builder.Configuration.GetSection(CdnOptions.Position));
builder.Services.Configure<ManifestOptions>(builder.Configuration.GetSection(ManifestOptions.Position));

builder.Services.AddControllers();
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<BuildDirectoryManager>();
builder.Services.AddSingleton<DownloadRequestLogger>();
builder.Services.AddHostedService(services => services.GetRequiredService<DownloadRequestLogger>());
Expand Down
Loading

0 comments on commit 318205a

Please sign in to comment.