namespace Sonex.Services.WebApi.Helpers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; using Microsoft.AspNetCore.Builder; using System.Globalization; using System.Net; using System.Text; public sealed class BetterDirectoryFormatter : Microsoft.AspNetCore.StaticFiles.IDirectoryFormatter { private readonly FileExtensionContentTypeProvider _contentTypes = new(); private readonly Func? _filter; /// np. ukryj pliki zaczynające się od "." albo "~" public BetterDirectoryFormatter(Func? filter = null) => _filter = filter; public async Task GenerateContentAsync(HttpContext context, IEnumerable contents) { // path w obrębie RequestPath, np. "/files/sub/a/" var requestPath = context.Request.Path.Value ?? "/"; if (!requestPath.EndsWith("/")) requestPath += "/"; var items = contents .Where(x => _filter?.Invoke(x) ?? true) .OrderByDescending(x => x.IsDirectory) // katalogi pierwsze .ThenByDescending(x => x.Name, StringComparer.OrdinalIgnoreCase) .ToList(); var sb = new StringBuilder(32_000); // ==== HTML head ==== sb.Append(""" Przeglądanie: """); sb.Append(WebUtility.HtmlEncode(requestPath)); sb.Append(""" """); // ==== header + crumbs ==== sb.Append("
"); sb.Append("
"); sb.Append(WebUtility.HtmlEncode(requestPath)); sb.Append("
"); sb.Append("
"); sb.Append(BuildBreadcrumbsHtml(context, requestPath)); sb.Append("
"); // ==== table ==== sb.Append(""" """); // link do góry (jeśli nie jesteśmy w root requestpath) var parentLink = GetParentLink(requestPath); if (parentLink is not null) { sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); } foreach (var it in items) { var href = requestPath + Uri.EscapeDataString(it.Name) + (it.IsDirectory ? "/" : ""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); } sb.Append("""
Name Size Release Date
"); sb.Append("📁 "); sb.Append($".."); sb.Append("
"); sb.Append(""); sb.Append(it.IsDirectory ? "📁" : "📄"); sb.Append(" "); sb.Append(""); sb.Append(WebUtility.HtmlEncode(it.Name)); sb.Append(""); if (it.IsDirectory) sb.Append("dir"); else { if (_contentTypes.TryGetContentType(it.Name, out var ct)) { sb.Append(""); sb.Append(WebUtility.HtmlEncode(ct)); sb.Append(""); } } sb.Append(""); sb.Append(it.IsDirectory ? "—" : FormatBytes(it.Length)); sb.Append(""); // IFileInfo nie zawsze ma LastModified? -> ma sb.Append(it.LastModified == default ? "—" : it.LastModified.ToLocalTime().ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture)); sb.Append("
"""); context.Response.ContentType = "text/html; charset=utf-8"; await context.Response.WriteAsync(sb.ToString()); } private static string? GetParentLink(string requestPath) { // requestPath zawsze kończy się "/" // "/files/" => null var trimmed = requestPath.TrimEnd('/'); var lastSlash = trimmed.LastIndexOf('/'); if (lastSlash <= 0) return null; var parent = trimmed.Substring(0, lastSlash + 1); return parent; } private static string BuildBreadcrumbsHtml(HttpContext context, string requestPath) { // RequestPath bazowy (np. "/files") + reszta // Tu składamy linki narastająco: files / sub / sub2 var path = requestPath.Trim('/'); if (string.IsNullOrWhiteSpace(path)) return ""; var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries); var sb = new StringBuilder(); var acc = "/"; for (int i = 0; i < parts.Length; i++) { acc += parts[i] + "/"; if (i > 0) sb.Append(" / "); sb.Append(""); sb.Append(WebUtility.HtmlEncode(parts[i])); sb.Append(""); } return sb.ToString(); } private static string FormatBytes(long bytes) { const double KB = 1024.0; const double MB = KB * 1024.0; const double GB = MB * 1024.0; if (bytes < 1024) return bytes.ToString(CultureInfo.InvariantCulture) + " B"; if (bytes < MB) return (bytes / KB).ToString("0.0", CultureInfo.InvariantCulture) + " KB"; if (bytes < GB) return (bytes / MB).ToString("0.0", CultureInfo.InvariantCulture) + " MB"; return (bytes / GB).ToString("0.0", CultureInfo.InvariantCulture) + " GB"; } }