a fork of iceshrimp.net but a tweaked frontend to my personal liking. waow
fediverse social-media social iceshrimp fedi
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at dev 179 lines 6.0 kB view raw
1using System.Globalization; 2using System.Text.Json; 3using System.Text.Json.Serialization; 4using Microsoft.Build.Framework; 5 6// ReSharper disable UnusedMember.Local 7// ReSharper disable CheckNamespace 8// ReSharper disable ClassNeverInstantiated.Local 9// ReSharper disable ConvertToAutoProperty 10// ReSharper disable PropertyCanBeMadeInitOnly.Local 11 12namespace Iceshrimp.Build.Tasks; 13 14public class RewriteStaticAssetManifest : Microsoft.Build.Utilities.Task 15{ 16 [System.ComponentModel.DataAnnotations.Required] 17 public required ITaskItem Manifest { get; set; } 18 19 public override bool Execute() 20 { 21 var parsed = Parse(Manifest.ItemSpec); 22 var @fixed = Fixup(Manifest.ItemSpec, parsed); 23 Write(Manifest.ItemSpec, @fixed); 24 return true; 25 } 26 27 private static StaticAssetsManifest Parse(string manifestPath) 28 { 29 using var stream = File.OpenRead(manifestPath); 30 using var reader = new StreamReader(stream); 31 var content = reader.ReadToEnd(); 32 33 // @formatter:off 34 var result = JsonSerializer.Deserialize<StaticAssetsManifest>(content) ?? 35 throw new InvalidOperationException($"The static resources manifest file '{manifestPath}' could not be deserialized."); 36 // @formatter:on 37 38 return result; 39 } 40 41 private static void Write(string manifestPath, StaticAssetsManifest manifest) 42 { 43 File.Delete(manifestPath); 44 using var stream = File.OpenWrite(manifestPath); 45 JsonSerializer.Serialize(stream, manifest, new JsonSerializerOptions { WriteIndented = true }); 46 } 47 48 private static StaticAssetsManifest Fixup(string manifestPath, StaticAssetsManifest manifest) 49 { 50 // Get a list of constrained routes 51 var brotliRoutes = manifest.Endpoints 52 .Where(p => p.Selectors is [{ Name: "Content-Encoding", Value: "br" }]) 53 .DistinctBy(p => p.AssetPath) 54 .ToDictionary(p => p.AssetPath, 55 p => p.ResponseHeaders 56 .FirstOrDefault(i => i.Name == "Content-Length")); 57 58 var arr = manifest.Endpoints.ToArray(); 59 60 // Rewrite uncompressed versions to reference brotli-compressed asset instead 61 foreach (var endpoint in arr) 62 { 63 if (endpoint.Selectors.Count > 0) continue; 64 if (!brotliRoutes.TryGetValue(endpoint.AssetPath + ".br", out var len)) 65 continue; 66 67 if (len is null) throw new Exception($"Couldn't find content-length for route ${endpoint.Route}"); 68 var origLen = endpoint.ResponseHeaders.First(p => p.Name == len.Name); 69 endpoint.Properties.Add(new StaticAssetProperty("Uncompressed-Length", origLen.Value)); 70 endpoint.ResponseHeaders.Remove(origLen); 71 endpoint.ResponseHeaders.Add(len); 72 endpoint.AssetPath += ".br"; 73 } 74 75 // Fixup assets without a corresponding compressed selector entry 76 foreach (var endpoint in arr) 77 { 78 if (endpoint.Route == endpoint.AssetPath) continue; 79 if (endpoint.Selectors is not []) continue; 80 if (!endpoint.AssetPath.EndsWith(".br")) continue; 81 if (endpoint.Route.EndsWith(".br")) continue; 82 if (endpoint.ResponseHeaders.FirstOrDefault(p => p.Name == "Content-Length") is not { } lenHdr) 83 continue; 84 if (!long.TryParse(lenHdr.Value, out var len)) 85 continue; 86 if (arr.Any(p => p.Route == endpoint.Route && p.AssetPath == endpoint.AssetPath && endpoint.Selectors is not [])) 87 continue; 88 89 var quality = Math.Round(1.0 / (len + 1), 12).ToString("F12", CultureInfo.InvariantCulture); 90 91 manifest.Endpoints.Add(new StaticAssetDescriptor 92 { 93 Route = endpoint.Route, 94 AssetPath = endpoint.AssetPath, 95 Properties = endpoint.Properties, 96 ResponseHeaders = [..endpoint.ResponseHeaders, new StaticAssetResponseHeader("Content-Encoding", "br")], 97 Selectors = [new StaticAssetSelector("Content-Encoding", "br", quality)] 98 }); 99 } 100 101 // Remove explicit routes 102 manifest.Endpoints.RemoveAll(p => p.Route.EndsWith(".br")); 103 104 // Clean up endpoints 105 var path = Path.GetDirectoryName(manifestPath) ?? throw new Exception("Invalid path"); 106 manifest.Endpoints.RemoveAll(p => !File.Exists(Path.Combine(path, "wwwroot", p.AssetPath))); 107 return manifest; 108 } 109 110 private class StaticAssetsManifest 111 { 112 public int Version { get; set; } 113 114 public string ManifestType { get; set; } = ""; 115 116 // ReSharper disable once CollectionNeverUpdated.Local 117 public List<StaticAssetDescriptor> Endpoints { get; set; } = []; 118 } 119 120 private sealed class StaticAssetDescriptor 121 { 122 private List<StaticAssetSelector> _selectors = []; 123 private List<StaticAssetProperty> _endpointProperties = []; 124 private List<StaticAssetResponseHeader> _responseHeaders = []; 125 126 public required string Route 127 { 128 get => field ?? throw new InvalidOperationException("Route is required"); 129 set; 130 } 131 132 [JsonPropertyName("AssetFile")] 133 public required string AssetPath 134 { 135 get => field ?? throw new InvalidOperationException("AssetPath is required"); 136 set; 137 } 138 139 [JsonPropertyName("Selectors")] 140 public List<StaticAssetSelector> Selectors 141 { 142 get => _selectors; 143 set => _selectors = value; 144 } 145 146 [JsonPropertyName("EndpointProperties")] 147 public List<StaticAssetProperty> Properties 148 { 149 get => _endpointProperties; 150 set => _endpointProperties = value; 151 } 152 153 [JsonPropertyName("ResponseHeaders")] 154 public List<StaticAssetResponseHeader> ResponseHeaders 155 { 156 get => _responseHeaders; 157 set => _responseHeaders = value; 158 } 159 } 160 161 private sealed class StaticAssetSelector(string name, string value, string quality) 162 { 163 public string Name { get; } = name; 164 public string Value { get; } = value; 165 public string Quality { get; } = quality; 166 } 167 168 private sealed class StaticAssetProperty(string name, string value) 169 { 170 public string Name { get; } = name; 171 public string Value { get; } = value; 172 } 173 174 private sealed class StaticAssetResponseHeader(string name, string value) 175 { 176 public string Name { get; } = name; 177 public string Value { get; } = value; 178 } 179}