mirror of
https://github.com/spacebarchat/server.git
synced 2026-10-22 17:32:30 +00:00
Functioning CDN-CS workers
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"/>
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
)
|
||||
)
|
||||
Reference in new issue
Block a user