mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
110 lines
4.4 KiB
C#
110 lines
4.4 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using RadicadeServer.Storage;
|
|
|
|
namespace RadicadeServer.API.Studio;
|
|
|
|
public sealed record StudioBakedUnityAsset(string UnityAssetId, byte Target, int Version, string Filename, string? RejectedDependenciesFilename = null);
|
|
|
|
public sealed record StudioUnityAsset(
|
|
string UnityAssetId,
|
|
int? CreatedByAccountId,
|
|
string Filename,
|
|
string Hash,
|
|
IReadOnlyList<StudioBakedUnityAsset> BakedUnityAssets);
|
|
|
|
public static class StudioUnityAssetRegistry
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
private static readonly SemaphoreSlim Gate = new(1, 1);
|
|
|
|
public static bool IsValidAssetId(string? value) =>
|
|
value is { Length: 40 } && value.All(char.IsAsciiLetterOrDigit);
|
|
|
|
public static async Task<StudioUnityAsset?> ReadAsync(string assetId, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!IsValidAssetId(assetId)) return null;
|
|
var manifestPath = Path.Combine(DBConfig.CDNStorage, assetId);
|
|
if (!File.Exists(manifestPath)) return null;
|
|
|
|
byte[] hash;
|
|
await using (var manifest = File.OpenRead(manifestPath))
|
|
hash = await SHA256.HashDataAsync(manifest, cancellationToken);
|
|
|
|
var path = MetadataPath(assetId);
|
|
IReadOnlyList<StudioBakedUnityAsset> baked = Array.Empty<StudioBakedUnityAsset>();
|
|
if (File.Exists(path))
|
|
{
|
|
try
|
|
{
|
|
var metadata = JsonSerializer.Deserialize<Metadata>(await File.ReadAllTextAsync(path, cancellationToken), JsonOptions);
|
|
if (metadata?.BakedUnityAssets != null) baked = metadata.BakedUnityAssets;
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
return new StudioUnityAsset(assetId, null, assetId, Convert.ToBase64String(hash), baked);
|
|
}
|
|
|
|
public static async Task<StudioBakedUnityAsset?> ResolveBundleAsync(string assetId, int target, int version, CancellationToken cancellationToken = default)
|
|
{
|
|
var asset = await ReadAsync(assetId, cancellationToken);
|
|
return asset?.BakedUnityAssets.FirstOrDefault(x => x.Target == target && x.Version == version);
|
|
}
|
|
|
|
public static async Task AddBakedAssetAsync(string assetId, StudioBakedUnityAsset bakedAsset, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!IsValidAssetId(assetId) || !IsValidAssetId(bakedAsset.UnityAssetId) || !IsValidAssetId(bakedAsset.Filename) ||
|
|
bakedAsset.Version < 0 || !File.Exists(Path.Combine(DBConfig.CDNStorage, bakedAsset.Filename)) ||
|
|
(bakedAsset.RejectedDependenciesFilename != null &&
|
|
(!IsValidAssetId(bakedAsset.RejectedDependenciesFilename) ||
|
|
!File.Exists(Path.Combine(DBConfig.CDNStorage, bakedAsset.RejectedDependenciesFilename)))))
|
|
throw new InvalidDataException("Invalid Studio Unity asset metadata.");
|
|
await Gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
Directory.CreateDirectory(DBConfig.CDNStorage);
|
|
var path = MetadataPath(assetId);
|
|
Metadata current = new();
|
|
if (File.Exists(path))
|
|
{
|
|
try
|
|
{
|
|
current = JsonSerializer.Deserialize<Metadata>(await File.ReadAllTextAsync(path, cancellationToken), JsonOptions) ?? new Metadata();
|
|
}
|
|
catch (JsonException) { }
|
|
}
|
|
|
|
var assets = current.BakedUnityAssets
|
|
.Where(x => x.Target != bakedAsset.Target || x.Version != bakedAsset.Version)
|
|
.Append(bakedAsset)
|
|
.ToArray();
|
|
var temporary = path + ".tmp-" + Guid.NewGuid().ToString("N");
|
|
try
|
|
{
|
|
await File.WriteAllTextAsync(temporary, JsonSerializer.Serialize(new Metadata(assets), JsonOptions), cancellationToken);
|
|
File.Move(temporary, path, overwrite: true);
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(temporary)) File.Delete(temporary);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Gate.Release();
|
|
}
|
|
}
|
|
|
|
private static string MetadataPath(string assetId) =>
|
|
Path.Combine(DBConfig.CDNStorage, $".studio-unityasset-{assetId}.json");
|
|
|
|
private sealed record Metadata(IReadOnlyList<StudioBakedUnityAsset> BakedUnityAssets)
|
|
{
|
|
public Metadata() : this(Array.Empty<StudioBakedUnityAsset>()) { }
|
|
}
|
|
}
|