diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Controllers/ImageResizeController.cs b/extra/admin-api/Spacebar.Cdn.Worker/Controllers/ImageResizeController.cs new file mode 100644 index 000000000..7ba269655 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Controllers/ImageResizeController.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Spacebar.Cdn.Worker.Controllers; + +[ApiController] +public class ImageResizeController : ControllerBase { + +} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn.Worker/DefaultAvatarRenderer.cs b/extra/admin-api/Spacebar.Cdn.Worker/DefaultAvatarRenderer.cs new file mode 100644 index 000000000..bf41d30b8 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/DefaultAvatarRenderer.cs @@ -0,0 +1,47 @@ +using ArcaneLibs.Extensions; +using ImageMagick; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; + +namespace Spacebar.AdminApi.TestClient.Services.Helpers; + +public class DefaultAvatarRenderer { + public static readonly (byte r, byte g, byte b)[] DefaultAvatarColors = [ + (70, 73, 236), + (150, 150, 150), + (236, 52, 83), + (211, 52, 236), + (45, 202, 76), + (236, 103, 52) + ]; + + public static readonly (byte r, byte g, byte b) SpacebarLogoColor = (0x01, 0x85, 0xFF); + + // Slower at runtime, but doesnt depend on filesystem + private static string GetDefaultAvatarSvg(byte r, byte g, byte b, byte rf = 0xff, byte gf = 0xff, byte bf = 0xff) { + return $""" + + + + + """; + } + + public static async Task GetDefaultAvatarImage(byte r, byte g, byte b, byte rf = 0xff, byte gf = 0xff, byte bf = 0xff, int size = 4096) { + var img = new MagickImageCollection(GetDefaultAvatarSvg(r, g, b, rf, gf, bf).AsBytes().ToArray(), new MagickReadSettings() { + Width = (uint?)size, + Height = (uint?)size, + // Verbose = true, + // ColorSpace = ColorSpace.RGB + }); + return img; + } + + public static async Task GetDefaultAvatar(byte r, byte g, byte b, byte rf = 0xff, byte gf = 0xff, byte bf = 0xff, MagickFormat format = MagickFormat.Png, + int size = 4096) { + var img = await GetDefaultAvatarImage(r, g, b, rf, gf, bf, size); + var ms = new MemoryStream(); + await img.WriteAsync(ms, format); + ms.Position = 0; + return ms; + } +} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Mimes.cs b/extra/admin-api/Spacebar.Cdn.Worker/Mimes.cs new file mode 100644 index 000000000..8337e81d9 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Mimes.cs @@ -0,0 +1,50 @@ +using System.ComponentModel; +using ImageMagick; + +namespace Spacebar.Cdn.Worker; + +// Keep up to date with CDN! +public static class Mimes { + private static string PrintLogged(string msg, string mime) { + Console.WriteLine($"{msg}: {mime}"); + return mime; + } + + public static MagickFormat GetFormatForExtension(string extension) { + extension = extension.ToLower(); + // ban some values... + // TODO: look for more + if (extension + // screen capture/write + is "screenshot" + or "win" + or "clipboard" + or "x" // read from/write to x11 server + or "xwd" // x11 window dump + or "dds" // MS DirectDraw surface + or "open" // display image on screen, OSX only + // printer stuff + or "print" + or "scan" + or "scanx" + // special + or "dmr" // MagicCache media library, let's not... + or "emf" // some microsoft meta format, windows only + or "mpr" // Magick Persistent Registry - basically a resident in-memory image + ) throw new AccessViolationException("Disallowed extension: " + extension); + + var matchingFormat = Enum.GetNames().FirstOrDefault(f => f.ToLower() == extension); + if (string.IsNullOrWhiteSpace(matchingFormat)) throw new InvalidEnumArgumentException("Unknown format: " + extension); + return Enum.TryParse(matchingFormat, out MagickFormat fmt) ? fmt : throw new InvalidEnumArgumentException("Unknown format: " + extension); + } + + public static string GetMime(MagickFormat fmt) => fmt switch { + MagickFormat.Png => "image/png", + MagickFormat.Jpeg => "image/jpeg", + MagickFormat.Gif => "image/gif", + MagickFormat.Bmp => "image/bmp", + MagickFormat.Tiff => "image/tiff", + MagickFormat.WebP => "image/webp", + _ => PrintLogged("Unknown mime for format " + fmt.ToString() + "!", "application/octet-stream") + }; +} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Program.cs b/extra/admin-api/Spacebar.Cdn.Worker/Program.cs new file mode 100644 index 000000000..fc3aaf932 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Program.cs @@ -0,0 +1,88 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using ArcaneLibs; +using ImageMagick; +using Spacebar.AdminApi.TestClient.Services.Helpers; +using Spacebar.Cdn.Worker; + +var builder = WebApplication.CreateBuilder(args); + +var sw = Stopwatch.StartNew(); +Console.WriteLine("Pre-initializing Magick.NET..."); +// OpenCL.IsEnabled = true; +MagickNET.Initialize(); +// Console.WriteLine("==> Rendering default avatars..."); +// foreach (var (r, g, b) in DefaultAvatarRenderer.DefaultAvatarColors) { +// var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, size: 4096); +// Console.WriteLine($" ==> #{r:X2}{g:X2}{b:X2} => {res.Length} bytes"); +// res.Position = 0; +// await using (var fs = File.OpenWrite($"default-{r:X2}{g:X2}{b:X2}.png")) { +// await res.CopyToAsync(fs); +// fs.Flush(); +// fs.Close(); +// } +// } + +// byte skip = 8; +// var re = new RainbowEnumerator(lengthFactor: 256, skip: skip); +// var reFg = new RainbowEnumerator(lengthFactor: 512, skip: skip, offset: 128); +// var magickCollection = new MagickImageCollection(); +// for (int i = 0; i < 255; i += skip) { +// var sw2 = Stopwatch.StartNew(); +// var clr = re.Next(); +// var clrFg = reFg.Next(); +// var res = await DefaultAvatarRenderer.GetDefaultAvatarImage(clr.r, clr.g, clr.b, clrFg.r, clrFg.g, clrFg.b, size: 512); +// Console.Write($" ==> #{clr.r:X2}{clr.g:X2}{clr.b:X2}/{clrFg.r:X2}{clrFg.g:X2}{clrFg.b:X2} ({i} => {magickCollection.Count + 1})... R"); +// res.Flatten(); +// Console.Write("F"); +// res.First().AnimationDelay = 4; +// Console.Write("A"); +// magickCollection.Add(res.First()); +// Console.WriteLine(" => " + sw2.Elapsed); +// } +// +// Console.WriteLine(" ==> Optimizing (1/2)..."); +// magickCollection.OptimizePlus(); +// Console.WriteLine(" ==> Optimizing (2/2)..."); +// magickCollection.OptimizeTransparency(); +// Console.WriteLine(" ==> Writing..."); +// await using (var fs = File.OpenWrite($"default-animated.gif")) { +// await magickCollection.WriteAsync(fs, MagickFormat.Gif); +// } +// +// Console.WriteLine(sw.Elapsed); +// Environment.Exit(0); + +// builder.WebHost.ConfigureKestrel(opts => opts.ListenUnixSocket(Environment.GetEnvironmentVariable("SOCKET_PATH")!)); + +builder.Services.AddControllers(); +var app = builder.Build(); +app.MapControllers(); + +app.MapGet("/defaultAvatar/{idx:int}.{ext}", async (HttpContext ctx, int idx, string ext) => { + var (r, g, b) = DefaultAvatarRenderer.DefaultAvatarColors[idx % DefaultAvatarRenderer.DefaultAvatarColors.Length]; + var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, size: ctx.Request.Query.ContainsKey("size") ? int.Parse(ctx.Request.Query["size"]!) : 4096, + format: Mimes.GetFormatForExtension(ext)); + return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext))); +}); + +// small easter egg internal stuff, maybe used someday :) +app.MapGet("/defaultAvatar/_{bg:length(6)}.{ext}", async (HttpContext ctx, string bg, string ext) => { + var (r, g, b) = (byte.Parse(bg[..2], NumberStyles.HexNumber), byte.Parse(bg[2..4], NumberStyles.HexNumber), byte.Parse(bg[4..6], NumberStyles.HexNumber)); + var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, size: ctx.Request.Query.ContainsKey("size") ? int.Parse(ctx.Request.Query["size"]!) : 4096, + format: Mimes.GetFormatForExtension(ext)); + return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext))); +}); + +app.MapGet("/defaultAvatar/_{bg:length(6)}_{fg:length(6)}.{ext}", async (HttpContext ctx, string bg, string fg, string ext) => { + var (r, g, b) = (byte.Parse(bg[..2], NumberStyles.HexNumber), byte.Parse(bg[2..4], NumberStyles.HexNumber), byte.Parse(bg[4..6], NumberStyles.HexNumber)); + var (rf, gf, bf) = (byte.Parse(fg[..2], NumberStyles.HexNumber), byte.Parse(fg[2..4], NumberStyles.HexNumber), byte.Parse(fg[4..6], NumberStyles.HexNumber)); + var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, rf, gf, bf, size: ctx.Request.Query.ContainsKey("size") ? int.Parse(ctx.Request.Query["size"]!) : 4096, + format: Mimes.GetFormatForExtension(ext)); + return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext))); +}); + +app.MapGet("/*", async (ctx) => { }); + +app.Run(); \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Properties/launchSettings.json b/extra/admin-api/Spacebar.Cdn.Worker/Properties/launchSettings.json new file mode 100644 index 000000000..7d6ece9c6 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5280", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj new file mode 100644 index 000000000..2c2b794b8 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj new file mode 100644 index 000000000..16d278df8 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj new file mode 100644 index 000000000..34f49b8e1 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj new file mode 100644 index 000000000..48e671ed5 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj new file mode 100644 index 000000000..781068a6d --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj new file mode 100644 index 000000000..16a2d1157 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj new file mode 100644 index 000000000..663e16c66 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj new file mode 100644 index 000000000..326ed11e7 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj new file mode 100644 index 000000000..1943e7b0b --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + Spacebar.Cdn.Worker + true + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/Spacebar.Cdn.Worker/appsettings.Development.json b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.Development.json new file mode 100644 index 000000000..dc0a4c8cc --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.Development.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information", + "Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware": "Information", + "Microsoft.AspNetCore": "Trace", //Warning + "Microsoft.AspNetCore.Mvc": "Warning", //Warning + "Microsoft.AspNetCore.HostFiltering": "Warning", //Warning + "Microsoft.AspNetCore.Cors": "Warning", //Warning + "Microsoft.AspNetCore.server.Kestrel": "Information", + "Microsoft.AspNetCore.Routing.Matching.DfaMatcher": "Information", + // "Microsoft.EntityFrameworkCore": "Warning" + "Microsoft.EntityFrameworkCore.Database.Command": "Debug" + } + } +} diff --git a/extra/admin-api/Spacebar.Cdn.Worker/appsettings.json b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/extra/admin-api/SpacebarAdminAPI.slnx b/extra/admin-api/SpacebarAdminAPI.slnx index 0578bc353..e2ec0b008 100644 --- a/extra/admin-api/SpacebarAdminAPI.slnx +++ b/extra/admin-api/SpacebarAdminAPI.slnx @@ -1,41 +1,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extra/admin-api/update-deps.cs b/extra/admin-api/update-deps.cs index 9aa1b0fe3..592bbf11f 100755 --- a/extra/admin-api/update-deps.cs +++ b/extra/admin-api/update-deps.cs @@ -7,6 +7,29 @@ using ArcaneLibs; using ArcaneLibs.Extensions; using System.Text.Json; +// sync versions for CDN worker +{ + Console.WriteLine("==> Ensuring CDN worker dependencies are in sync..."); + var origContent = await File.ReadAllTextAsync("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj"); + var depToReplace = "Magick.NET-Q16-HDRI-OpenMP-x64"; + (string Project, string Dependency)[] replaceTargets = [ + // ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj", "Magick.NET-Q16-HDRI-OpenMP-x64"), // source + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj", "Magick.NET-Q16-OpenMP-x64"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj", "Magick.NET-Q8-OpenMP-x64"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj", "Magick.NET-Q16-HDRI-OpenMP-arm64"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj", "Magick.NET-Q16-OpenMP-arm64"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj", "Magick.NET-Q8-OpenMP-arm64"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj", "Magick.NET-Q16-HDRI-AnyCPU"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj", "Magick.NET-Q16-AnyCPU"), + ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj", "Magick.NET-Q8-AnyCPU"), + ]; + + foreach (var target in replaceTargets) { + Console.WriteLine($" ==> {target.Project} -> {target.Dependency}"); + await File.WriteAllTextAsync(target.Project, origContent.Replace(depToReplace, target.Dependency)); + } +} + Console.WriteLine("==> Getting outputs..."); var outs = JsonSerializer.Deserialize(Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux --apply builtins.attrNames", silent: true, stderr: false)); if (args.Length > 0) {