Functioning CDN-CS workers

This commit is contained in:
Rory& committed 2026-04-11 15:41:18 +02:00
1 parent 7b382646e4
commit 6b429fcde9
10 files changed
+283 -47

No files matched your search

@@ -1,20 +1,27 @@
using ArcaneLibs;
namespace Spacebar.Interop.Cdn.Abstractions;
public class FilesystemFileSource(string baseUrl) : IFileSource {
public string BaseUrl => baseUrl;
public async Task Init(CancellationToken? cancellationToken = null) {
private string GetPathSafe(string path) {
var p = Path.Join(baseUrl, path);
if (!p.StartsWith(baseUrl)) throw new UnauthorizedAccessException("Invalid path: " + p);
Console.WriteLine($"Resolved \"safe\" path: {p}");
return p;
}
public async Task<IFileSource> Init(CancellationToken? cancellationToken = null) {
foreach (var dir in new[] { "avatars", "banners", "icons", "stickers", "emojis" }) {
var fullPath = Path.Join(baseUrl, dir);
if (!Directory.Exists(fullPath))
Directory.CreateDirectory(fullPath);
}
return this;
}
public async Task<FileInfo> GetFile(string path, CancellationToken? cancellationToken = null) {
await using var rs = File.OpenRead(Path.Join(baseUrl, path));
await using var rs = File.OpenRead(GetPathSafe(path));
var ms = new MemoryStream();
await rs.CopyToAsync(ms);
return new() {
@@ -24,11 +31,11 @@ public class FilesystemFileSource(string baseUrl) : IFileSource {
}
public Task<bool> FileExists(string path, CancellationToken? cancellationToken = null) {
return Task.FromResult(File.Exists(Path.Join(baseUrl, path)));
return Task.FromResult(File.Exists(GetPathSafe(path)));
}
public async Task WriteFile(string path, Stream stream) {
var fullPath = Path.Join(baseUrl, path);
var fullPath = GetPathSafe(path);
// Console.WriteLine($"Writing file to {fullPath}... ");
if (!Directory.Exists(Path.GetDirectoryName(fullPath)!))
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
@@ -43,6 +50,6 @@ public class FilesystemFileSource(string baseUrl) : IFileSource {
// return Mimes.GetMime(mic.First().Format);
// }
public Task<bool> DirectoryExists(string path) {
return Task.FromResult(Directory.Exists(Path.Join(baseUrl, path)));
return Task.FromResult(Directory.Exists(GetPathSafe(path)));
}
}
@@ -2,7 +2,7 @@ namespace Spacebar.Interop.Cdn.Abstractions;
public interface IFileSource {
public string BaseUrl { get; }
public Task Init(CancellationToken? cancellationToken = null);
public Task<IFileSource> Init(CancellationToken? cancellationToken = null);
public Task<FileInfo> GetFile(string path, CancellationToken? cancellationToken = null);
public Task<bool> FileExists(string path, CancellationToken? cancellationToken = null);
public Task WriteFile(string path, Stream stream);
@@ -18,7 +18,9 @@ public class ProxyFileSource(string baseUrl) : IFileSource {
public string BaseUrl => baseUrl;
public async Task Init(CancellationToken? cancellationToken = null) { }
public async Task<IFileSource> Init(CancellationToken? cancellationToken = null) {
return this;
}
public async Task<FileInfo> GetFile(string path, CancellationToken? cancellationToken = null) {
var res = await _cache.GetOrAdd(path, async () => {
@@ -44,6 +46,7 @@ public class ProxyFileSource(string baseUrl) : IFileSource {
await using var s = await res.Content.ReadAsStreamAsync();
Console.WriteLine($"Got {res.StatusCode}: ({res.Content.Headers.ContentLength}b of {res.Content.Headers.ContentType})\n{s.ReadToEnd().AsString()}");
}
return res.IsSuccessStatusCode;
}
@@ -0,0 +1,91 @@
using System.Runtime.Serialization;
using ImageMagick;
using Microsoft.AspNetCore.Mvc;
namespace Spacebar.AdminApi.TestClient.Services.Services;
public class DiscordImageResizeParams {
public uint? Size { get; set; }
public DiscordImageResizeQuality Quality { get; set; } = DiscordImageResizeQuality.High;
public bool KeepAspectRatio { get; set; } = true;
public bool Passthrough { get; set; } = true;
public bool Animated { get; set; } = true;
public bool SpacebarAllowUpscale { get; set; } = false;
public bool SpacebarOptimiseGif { get; set; } = true;
public string ToSerializedName() {
return $"{(Animated ? "a_" : "")}{Size}px_{Quality.ToString()}_u.{SpacebarAllowUpscale}_o.{SpacebarOptimiseGif}";
}
}
public static class HttpRequestExtensions {
extension(HttpRequest request) {
public DiscordImageResizeParams GetResizeParams() {
return new() {
Size = request.Query.ContainsKey("size") && uint.TryParse(request.Query["size"], out uint size) ? size : null,
Quality = request.Query.ContainsKey("quality") && Enum.TryParse<DiscordImageResizeQuality>(request.Query["quality"], true, out var quality)
? quality
: DiscordImageResizeQuality.High,
KeepAspectRatio = !request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(request.Query["keepAspectRatio"], out bool kar) || kar,
Passthrough = request.Query.ContainsKey("passthrough") && bool.TryParse(request.Query["passthrough"], out bool pt) && pt,
Animated = request.Query.ContainsKey("animated") && bool.TryParse(request.Query["animated"], out bool an) && an,
SpacebarAllowUpscale = request.Query.ContainsKey("allowUpscale") && bool.TryParse(request.Query["allowUpscale"], out bool au) && au,
SpacebarOptimiseGif = request.Query.ContainsKey("optimiseGif") && bool.TryParse(request.Query["optimiseGif"], out bool og) && og,
};
}
}
extension (HttpResponse response) {
public void SetSuccessCacheHeader() {
int cacheDuration = (int)TimeSpan.FromHours(6).TotalSeconds;
response.Headers.CacheControl = $"public, max-age={cacheDuration}, s-maxage={cacheDuration}, immutable";
}
public void SetFailureCacheHeader() {
int cacheDuration = (int)TimeSpan.FromMinutes(5).TotalSeconds;
response.Headers.CacheControl = $"public, max-age={cacheDuration}, s-maxage={cacheDuration}, immutable";
}
}
}
public enum DiscordImageResizeQuality {
[EnumMember(Value = "low")] Low,
[EnumMember(Value = "high")] High,
[EnumMember(Value = "lossless")] Lossless
}
public class DiscordImageResizeService {
//(PixelArtDetectionService pads) {
public MagickImageCollection Apply(MagickImageCollection img, DiscordImageResizeParams resizeParams) {
if (resizeParams.Passthrough) return img;
if (img.First().Format == MagickFormat.Gif) {
Console.WriteLine("Coalescing gif for resize");
img.Coalesce();
}
if (resizeParams.Size.HasValue) {
if (resizeParams.Size > 4096)
resizeParams.Size = 4096;
if (img.Max(x => Math.Max(x.Height, x.Width)) > resizeParams.Size || resizeParams.SpacebarAllowUpscale) {
Parallel.ForEach(img, new ParallelOptions() { MaxDegreeOfParallelism = 8 }, frame => {
if (resizeParams.Size.HasValue) {
uint oldWidth = frame.Width, oldHeight = frame.Height;
// pads.IsPixelArt(frame)
frame.Resize(resizeParams.Size.Value, resizeParams.Size.Value,
resizeParams.Quality == DiscordImageResizeQuality.Low ? FilterType.Point : FilterType.Gaussian);
Console.WriteLine($"Resized frame from {oldWidth}x{oldHeight} to {frame.Width}x{frame.Height}: {img.IndexOf(frame)}/{img.Count}");
}
});
}
}
if (img.First().Format == MagickFormat.Gif && resizeParams.SpacebarOptimiseGif) {
Console.WriteLine("Optimizing gif after resize");
img.OptimizePlus();
}
return img;
}
}
+13 -3
View File
@@ -2,8 +2,11 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using ArcaneLibs;
using ArcaneLibs.Extensions.Streams;
using ImageMagick;
using Microsoft.AspNetCore.Mvc;
using Spacebar.AdminApi.TestClient.Services.Helpers;
using Spacebar.AdminApi.TestClient.Services.Services;
using Spacebar.Cdn.Worker;
using Spacebar.Interop.Cdn.Abstractions;
@@ -57,8 +60,8 @@ MagickNET.Initialize();
// builder.WebHost.ConfigureKestrel(opts => opts.ListenUnixSocket(Environment.GetEnvironmentVariable("SOCKET_PATH")!));
builder.Services.AddSingleton<IFileSource>(new FilesystemFileSource(Environment.GetEnvironmentVariable("STORAGE_PATH") ?? throw new InvalidOperationException("STORAGE_PATH not set!")));
// builder.Services.AddSingleton<DiscordImageResizeService>();
builder.Services.AddSingleton<IFileSource>(await new FilesystemFileSource(Environment.GetEnvironmentVariable("STORAGE_PATH") ?? throw new InvalidOperationException("STORAGE_PATH not set!")).Init());
builder.Services.AddSingleton<DiscordImageResizeService>();
builder.Services.AddControllers();
var app = builder.Build();
@@ -87,6 +90,13 @@ app.MapGet("/defaultAvatar/_{bg:length(6)}_{fg:length(6)}.{ext}", async (HttpCon
return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext)));
});
app.MapGet("/*", async (ctx) => { });
app.MapGet("/scale/{*path}", async (HttpContext ctx, IFileSource ifs, DiscordImageResizeService dirs, string path) => {
var f = await ifs.GetFile(path);
f.Stream.Position = 0;
var res = dirs.Apply(new MagickImageCollection(f.Stream), ctx.Request.GetResizeParams());
await ctx.Response.StartAsync();
await res.WriteAsync(ctx.Response.Body);
await ctx.Response.CompleteAsync();
});
app.Run();
+7
View File
@@ -0,0 +1,7 @@
#! /usr/bin/env sh
#for s in {16,32,48,64,96,128,256,512,4096}; do time curl http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db\?size=$s\&quality\=high | kitty icat; done
for i in {1..6}; do
for s in {16,32,48,64,96,128,256,512,1024,2048,4096}; do
echo curl http://localhost:5000/defaultAvatar/${i}.png\?size=$s\&quality\=low #| kitty icat
done
done
@@ -28,7 +28,7 @@ flake-utils.lib.eachSystem flake-utils.lib.allSystems (
makeWorkerPackage =
arch: variant:
buildSpacebarDotnetModule {
name = "Spacebar.Cdn.Worker-${variant}.${arch}";
name = "Spacebar.Cdn.Worker.${variant}.${arch}";
nugetDeps = ./deps.${variant}.${arch}.json;
projectFile = "Spacebar.Cdn.Worker.${variant}.${arch}.csproj";
srcRoot = ./.;
@@ -36,6 +36,17 @@ flake-utils.lib.eachSystem flake-utils.lib.allSystems (
projectReferences = [
proj.Spacebar-Interop-Cdn-Abstractions
];
nativeBuildInputs = [
pkgs.autoPatchelfHook
pkgs.libgcc.lib
];
runtimeDependencies = [ (lib.getLib pkgs.libgcc) ];
#makeWrapperArgs = [
# "--prefix"
# "LD_PRELOAD"
# ":"
# "${pkgs.libgcc}/lib/libgomp.so"
#];
};
in
{
@@ -0,0 +1,91 @@
<img src="http://localhost:5000/defaultAvatar/1.png?size=16&quality=high" alt="1 s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=32&quality=high" alt="1 s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=48&quality=high" alt="1 s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=64&quality=high" alt="1 s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=96&quality=high" alt="1 s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=128&quality=high" alt="1 s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=256&quality=high" alt="1 s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=512&quality=high" alt="1 s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=1024&quality=high" alt="1 s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=2048&quality=high" alt="1 s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/1.png?size=4096&quality=high" alt="1 s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=16&quality=high" alt="2 s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=32&quality=high" alt="2 s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=48&quality=high" alt="2 s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=64&quality=high" alt="2 s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=96&quality=high" alt="2 s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=128&quality=high" alt="2 s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=256&quality=high" alt="2 s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=512&quality=high" alt="2 s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=1024&quality=high" alt="2 s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=2048&quality=high" alt="2 s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/2.png?size=4096&quality=high" alt="2 s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=16&quality=high" alt="3 s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=32&quality=high" alt="3 s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=48&quality=high" alt="3 s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=64&quality=high" alt="3 s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=96&quality=high" alt="3 s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=128&quality=high" alt="3 s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=256&quality=high" alt="3 s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=512&quality=high" alt="3 s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=1024&quality=high" alt="3 s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=2048&quality=high" alt="3 s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/3.png?size=4096&quality=high" alt="3 s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=16&quality=high" alt="4 s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=32&quality=high" alt="4 s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=48&quality=high" alt="4 s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=64&quality=high" alt="4 s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=96&quality=high" alt="4 s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=128&quality=high" alt="4 s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=256&quality=high" alt="4 s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=512&quality=high" alt="4 s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=1024&quality=high" alt="4 s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=2048&quality=high" alt="4 s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/4.png?size=4096&quality=high" alt="4 s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=16&quality=high" alt="5 s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=32&quality=high" alt="5 s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=48&quality=high" alt="5 s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=64&quality=high" alt="5 s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=96&quality=high" alt="5 s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=128&quality=high" alt="5 s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=256&quality=high" alt="5 s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=512&quality=high" alt="5 s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=1024&quality=high" alt="5 s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=2048&quality=high" alt="5 s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/5.png?size=4096&quality=high" alt="5 s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=16&quality=high" alt="6 s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=32&quality=high" alt="6 s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=48&quality=high" alt="6 s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=64&quality=high" alt="6 s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=96&quality=high" alt="6 s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=128&quality=high" alt="6 s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=256&quality=high" alt="6 s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=512&quality=high" alt="6 s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=1024&quality=high" alt="6 s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=2048&quality=high" alt="6 s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/defaultAvatar/6.png?size=4096&quality=high" alt="6 s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=16&quality=low" alt="a s=16 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=32&quality=low" alt="a s=32 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=48&quality=low" alt="a s=48 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=64&quality=low" alt="a s=64 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=96&quality=low" alt="a s=96 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=128&quality=low" alt="a s=128 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=256&quality=low" alt="a s=256 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=512&quality=low" alt="a s=512 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=1024&quality=low" alt="a s=1024 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=2048&quality=low" alt="a s=2048 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=4096&quality=low" alt="a s=4096 q=low" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=16&quality=high" alt="a s=16 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=32&quality=high" alt="a s=32 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=48&quality=high" alt="a s=48 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=64&quality=high" alt="a s=64 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=96&quality=high" alt="a s=96 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=128&quality=high" alt="a s=128 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=256&quality=high" alt="a s=256 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=512&quality=high" alt="a s=512 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=1024&quality=high" alt="a s=1024 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=2048&quality=high" alt="a s=2048 q=high" width="128" height="128"/>
<img src="http://localhost:5000/scale/avatars/1006598230156341276/cc0a23b479ef23641480d53a49f1b3db?size=4096&quality=high" alt="a s=4096 q=high" width="128" height="128"/>
+1 -2
View File
@@ -31,8 +31,7 @@ public static class Mimes {
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);
MagickFormat
var matchingFormat = Enum.GetNames<MagickFormat>().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);
+49 -32
View File
@@ -8,37 +8,54 @@
useAppHost ? null,
packNupkg ? true,
srcRoot ? ./.,
...
}@args:
pkgs.buildDotnetModule rec {
inherit
projectReferences
nugetDeps
projectFile
runtimeId
useAppHost
srcRoot
packNupkg
;
pkgs.buildDotnetModule (pkgs.lib.recursiveUpdate
(
rec {
inherit
projectReferences
nugetDeps
projectFile
runtimeId
useAppHost
srcRoot
packNupkg
;
pname = "${name}";
version = "1.0.0-" + rVersion;
dotnetPackFlags = [
"--include-symbols"
"--include-source"
"--version-suffix ${rVersion}"
];
# dotnetFlags = [ "-v:n" ]; # diag
dotnet-sdk = pkgs.dotnet-sdk_10;
dotnet-runtime = pkgs.dotnet-aspnetcore_10;
src = pkgs.lib.cleanSource srcRoot;
meta = with pkgs.lib; {
description = "Spacebar Server, Typescript Edition (C# extensions)";
homepage = "https://github.com/spacebarchat/server";
license = licenses.agpl3Plus;
maintainers = with maintainers; [ RorySys ];
mainProgram = name;
};
}
// {
__nugetDeps = args.nugetDeps;
}
pname = "${name}";
version = "1.0.0-" + rVersion;
dotnetPackFlags = [
"--include-symbols"
"--include-source"
"--version-suffix ${rVersion}"
];
# dotnetFlags = [ "-v:n" ]; # diag
dotnet-sdk = pkgs.dotnet-sdk_10;
dotnet-runtime = pkgs.dotnet-aspnetcore_10;
src = pkgs.lib.cleanSource srcRoot;
meta = with pkgs.lib; {
description = "Spacebar Server, Typescript Edition (C# extensions)";
homepage = "https://github.com/spacebarchat/server";
license = licenses.agpl3Plus;
maintainers = with maintainers; [ RorySys ];
mainProgram = name;
};
}
// {
__nugetDeps = args.nugetDeps;
}
)
(
builtins.removeAttrs args [
"name"
"nugetDeps"
"projectReferences"
"projectFile"
"runtimeId"
"useAppHost"
"packNupkg"
"srcRoot"
]
)
)