using Avalonia_EFCore.Database; using Microsoft.EntityFrameworkCore; namespace Avalonia_API.Extensions { public static class FileStreamEndpointExtensions { public static IEndpointRouteBuilder MapFileStreamEndpoints(this IEndpointRouteBuilder app) { app.MapMethods("/api/files/{id:int}/stream", ["GET", "HEAD"], async (int id, AppDataContext db, HttpContext httpContext) => { // Browsers cancel in-flight range requests aggressively while seeking. // Keep this small metadata lookup independent from RequestAborted so // EF does not throw TaskCanceledException before the file is opened. var file = await db.ManagedFileRecords .AsNoTracking() .Include(item => item.LibraryRoot) .FirstOrDefaultAsync(item => item.Id == id && item.Exists && item.LibraryRoot != null && item.LibraryRoot.IsAvailable); if (file is null || !System.IO.File.Exists(file.AbsolutePath)) { return Results.NotFound(); } var stream = System.IO.File.Open(file.AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); httpContext.Response.Headers.ContentDisposition = $"inline; filename=\"{Uri.EscapeDataString(file.FileName)}\""; httpContext.Response.Headers.AcceptRanges = "bytes"; httpContext.Response.Headers.CacheControl = "public, max-age=3600"; return Results.File( stream, contentType: file.ContentType, fileDownloadName: null, lastModified: file.LastWriteTimeUtc, enableRangeProcessing: true); }) .WithName("StreamManagedFile") .WithTags("FileLibrary"); return app; } } }