diff options
author | TheArcaneBrony <myrainbowdash949@gmail.com> | 2023-11-23 05:43:15 +0100 |
---|---|---|
committer | TheArcaneBrony <myrainbowdash949@gmail.com> | 2023-11-23 05:43:15 +0100 |
commit | d3d95bbb271902391cbd43a11a6a5d72b0ccfaef (patch) | |
tree | 29fb4dd65d855a66fa65755fbf8d443a6154dc80 /MxApiExtensions/Controllers | |
parent | Fix sync (diff) | |
download | MxApiExtensions-d3d95bbb271902391cbd43a11a6a5d72b0ccfaef.tar.xz |
Add more code
Diffstat (limited to 'MxApiExtensions/Controllers')
7 files changed, 205 insertions, 108 deletions
diff --git a/MxApiExtensions/Controllers/Client/Room/RoomController.cs b/MxApiExtensions/Controllers/Client/Room/RoomController.cs new file mode 100644 index 0000000..a3e433d --- /dev/null +++ b/MxApiExtensions/Controllers/Client/Room/RoomController.cs @@ -0,0 +1,51 @@ +using LibMatrix; +using LibMatrix.Services; +using Microsoft.AspNetCore.Mvc; +using MxApiExtensions.Services; + +namespace MxApiExtensions.Controllers.Client.Room; + +[ApiController] +[Route("/")] +public class RoomController(ILogger<LoginController> logger, HomeserverResolverService hsResolver, AuthenticationService auth, MxApiExtensionsConfiguration conf, + AuthenticatedHomeserverProviderService hsProvider) + : ControllerBase { + [HttpGet("/_matrix/client/{_}/rooms/{roomId}/members_by_homeserver")] + public async Task<Dictionary<string, List<string>>> GetRoomMembersByHomeserver(string _, [FromRoute] string roomId, [FromQuery] bool joinedOnly = true) { + var hs = await hsProvider.GetHomeserver(); + var room = hs.GetRoom(roomId); + return await room.GetMembersByHomeserverAsync(joinedOnly); + } + + /// <summary> + /// Fetches up to <paramref name="limit"/> timeline events + /// </summary> + /// <param name="_"></param> + /// <param name="roomId"></param> + /// <param name="from"></param> + /// <param name="limit"></param> + /// <param name="dir"></param> + /// <param name="filter"></param> + /// <param name="includeState"></param> + /// <param name="fixForward">Reverse load all messages and reverse on API side, fixes history starting at join event</param> + /// <returns></returns> + [HttpGet("/_matrix/client/{_}/rooms/{roomId}/mass_messages")] + public async IAsyncEnumerable<StateEventResponse> RedactUser(string _, [FromRoute] string roomId, [FromQuery(Name = "from")] string from = "", + [FromQuery(Name = "limit")] int limit = 100, [FromQuery(Name = "dir")] string dir = "b", [FromQuery(Name = "filter")] string filter = "", + [FromQuery(Name = "include_state")] bool includeState = true, [FromQuery(Name = "fix_forward")] bool fixForward = false) { + var hs = await hsProvider.GetHomeserver(); + var room = hs.GetRoom(roomId); + var msgs = room.GetManyMessagesAsync(from: from, limit: limit, dir: dir, filter: filter, includeState: includeState, fixForward: fixForward); + await foreach (var resp in msgs) { + Console.WriteLine($"GetMany messages returned {resp.Chunk.Count} timeline events and {resp.State.Count} state events, end={resp.End}"); + foreach (var timelineEvent in resp.Chunk) { + yield return timelineEvent; + } + + if (includeState) + foreach (var timelineEvent in resp.State) { + yield return timelineEvent; + } + } + } +} \ No newline at end of file diff --git a/MxApiExtensions/Controllers/Client/Room/RoomsSendMessageController.cs b/MxApiExtensions/Controllers/Client/Room/RoomsSendMessageController.cs index b756582..e882c8a 100644 --- a/MxApiExtensions/Controllers/Client/Room/RoomsSendMessageController.cs +++ b/MxApiExtensions/Controllers/Client/Room/RoomsSendMessageController.cs @@ -1,18 +1,73 @@ +using System.Buffers.Text; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Nodes; +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.EventTypes.Spec; +using LibMatrix.Extensions; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; using LibMatrix.Services; using Microsoft.AspNetCore.Mvc; +using MxApiExtensions.Classes; +using MxApiExtensions.Classes.LibMatrix; +using MxApiExtensions.Extensions; using MxApiExtensions.Services; -namespace MxApiExtensions.Controllers.Client.Room; +namespace MxApiExtensions.Controllers; [ApiController] [Route("/")] -public class RoomController(ILogger<LoginController> logger, HomeserverResolverService hsResolver, AuthenticationService auth, MxApiExtensionsConfiguration conf, - AuthenticatedHomeserverProviderService hsProvider) +public class RoomsSendMessageController(ILogger<LoginController> logger, UserContextService userContextService) : ControllerBase { - [HttpGet("/_matrix/client/{_}/rooms/{roomId}/members_by_homeserver")] - public async Task<Dictionary<string, List<string>>> GetRoomMembersByHomeserver(string _, [FromRoute] string roomId, [FromQuery] bool joinedOnly = true) { - var hs = await hsProvider.GetHomeserver(); - var room = hs.GetRoom(roomId); - return await room.GetMembersByHomeserverAsync(joinedOnly); + [HttpPut("/_matrix/client/{_}/rooms/{roomId}/send/m.room.message/{txnId}")] + public async Task Proxy([FromBody] JsonObject request, [FromRoute] string roomId, [FromRoute] string txnId, string _) { + var uc = await userContextService.GetCurrentUserContext(); + // var hs = await hsProvider.GetHomeserver(); + + var msg = request.Deserialize<RoomMessageEventContent>(); + if (msg is not null && msg.Body.StartsWith("mxae!")) { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + handleMxaeCommand(uc, roomId, msg); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + await Response.WriteAsJsonAsync(new EventIdResponse() { + EventId = "$" + string.Join("", Random.Shared.GetItems("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789".ToCharArray(), 100)) + }); + await Response.CompleteAsync(); + } + else { + try { + var resp = await uc.Homeserver.ClientHttpClient.PutAsJsonAsync($"{Request.Path}{Request.QueryString}", request); + await Response.WriteHttpResponse(resp); + // var loginResp = await resp.Content.ReadAsStringAsync(); + // Response.StatusCode = (int)resp.StatusCode; + // Response.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json"; + // await Response.StartAsync(); + // await Response.WriteAsync(loginResp); + // await Response.CompleteAsync(); + } + catch (MatrixException e) { + await Response.StartAsync(); + await Response.WriteAsync(e.GetAsJson()); + await Response.CompleteAsync(); + } + } + } + + private async Task handleMxaeCommand(UserContextService.UserContext hs, string roomId, RoomMessageEventContent msg) { + if (hs.SyncState is null) return; + hs.SyncState.SendEphemeralTimelineEventInRoom(roomId, new() { + Sender = "@mxae:" + Request.Host.Value, + Type = "m.room.message", + TypedContent = MessageFormatter.FormatSuccess("Thinking..."), + OriginServerTs = (ulong)new DateTimeOffset(DateTime.UtcNow.ToUniversalTime()).ToUnixTimeMilliseconds(), + Unsigned = new() { + Age = 1 + }, + RoomId = roomId, + EventId = "$" + string.Join("", Random.Shared.GetItems("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789".ToCharArray(), 100)) + }); } } \ No newline at end of file diff --git a/MxApiExtensions/Controllers/Client/RoomsSendMessageController.cs b/MxApiExtensions/Controllers/Client/RoomsSendMessageController.cs deleted file mode 100644 index e882c8a..0000000 --- a/MxApiExtensions/Controllers/Client/RoomsSendMessageController.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Buffers.Text; -using System.Net.Http.Headers; -using System.Text.Json; -using System.Text.Json.Nodes; -using ArcaneLibs.Extensions; -using LibMatrix; -using LibMatrix.EventTypes.Spec; -using LibMatrix.Extensions; -using LibMatrix.Helpers; -using LibMatrix.Homeservers; -using LibMatrix.Responses; -using LibMatrix.Services; -using Microsoft.AspNetCore.Mvc; -using MxApiExtensions.Classes; -using MxApiExtensions.Classes.LibMatrix; -using MxApiExtensions.Extensions; -using MxApiExtensions.Services; - -namespace MxApiExtensions.Controllers; - -[ApiController] -[Route("/")] -public class RoomsSendMessageController(ILogger<LoginController> logger, UserContextService userContextService) - : ControllerBase { - [HttpPut("/_matrix/client/{_}/rooms/{roomId}/send/m.room.message/{txnId}")] - public async Task Proxy([FromBody] JsonObject request, [FromRoute] string roomId, [FromRoute] string txnId, string _) { - var uc = await userContextService.GetCurrentUserContext(); - // var hs = await hsProvider.GetHomeserver(); - - var msg = request.Deserialize<RoomMessageEventContent>(); - if (msg is not null && msg.Body.StartsWith("mxae!")) { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - handleMxaeCommand(uc, roomId, msg); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - await Response.WriteAsJsonAsync(new EventIdResponse() { - EventId = "$" + string.Join("", Random.Shared.GetItems("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789".ToCharArray(), 100)) - }); - await Response.CompleteAsync(); - } - else { - try { - var resp = await uc.Homeserver.ClientHttpClient.PutAsJsonAsync($"{Request.Path}{Request.QueryString}", request); - await Response.WriteHttpResponse(resp); - // var loginResp = await resp.Content.ReadAsStringAsync(); - // Response.StatusCode = (int)resp.StatusCode; - // Response.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json"; - // await Response.StartAsync(); - // await Response.WriteAsync(loginResp); - // await Response.CompleteAsync(); - } - catch (MatrixException e) { - await Response.StartAsync(); - await Response.WriteAsync(e.GetAsJson()); - await Response.CompleteAsync(); - } - } - } - - private async Task handleMxaeCommand(UserContextService.UserContext hs, string roomId, RoomMessageEventContent msg) { - if (hs.SyncState is null) return; - hs.SyncState.SendEphemeralTimelineEventInRoom(roomId, new() { - Sender = "@mxae:" + Request.Host.Value, - Type = "m.room.message", - TypedContent = MessageFormatter.FormatSuccess("Thinking..."), - OriginServerTs = (ulong)new DateTimeOffset(DateTime.UtcNow.ToUniversalTime()).ToUnixTimeMilliseconds(), - Unsigned = new() { - Age = 1 - }, - RoomId = roomId, - EventId = "$" + string.Join("", Random.Shared.GetItems("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789".ToCharArray(), 100)) - }); - } -} \ No newline at end of file diff --git a/MxApiExtensions/Controllers/Client/SyncController.cs b/MxApiExtensions/Controllers/Client/SyncController.cs index 7f9ed1d..615502b 100644 --- a/MxApiExtensions/Controllers/Client/SyncController.cs +++ b/MxApiExtensions/Controllers/Client/SyncController.cs @@ -28,12 +28,14 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi private UserContextService.UserContext userContext; private Stopwatch _syncElapsed = Stopwatch.StartNew(); private static SemaphoreSlim _semaphoreSlim = new(1, 1); + public static List<Task> TrackedTasks { get; set; } = new(); [HttpGet("/_matrix/client/{_}/sync")] public async Task Sync(string _, [FromQuery] string? since, [FromQuery] int timeout = 1000) { // temporary variables bool startedNewTask = false; Task? preloadTask = null; + TrackedTasks.RemoveAll(x => x.Status == TaskStatus.RanToCompletion); // get user context based on authentication userContext = await userContextService.GetCurrentUserContext(); @@ -50,12 +52,12 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi //prevent duplicate initialisation await _semaphoreSlim.WaitAsync(); - + //if we don't have a sync state for this user... if (userContext.SyncState is null) { logger.LogInformation("Started tracking sync state for {} on {} ({})", userContext.Homeserver.WhoAmI.UserId, userContext.Homeserver.ServerName, userContext.Homeserver.AccessToken); - + //create a new sync state userContext.SyncState = new SyncState { Homeserver = userContext.Homeserver, @@ -68,7 +70,7 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi }) }; startedNewTask = true; - + //if this is an initial sync, and the user has enabled this, preload data if (string.IsNullOrWhiteSpace(since) && userContext.UserConfiguration.InitialSyncPreload.Enable) { logger.LogInformation("Sync data preload for {} on {} ({}) starting", userContext.Homeserver.WhoAmI.UserId, userContext.Homeserver.ServerName, @@ -102,11 +104,13 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi //await scope-local tasks in order to prevent disposal if (preloadTask is not null) { + TrackedTasks.Add(preloadTask); await preloadTask; preloadTask.Dispose(); } if (startedNewTask && userContext.SyncState?.NextSyncResponse is not null) { + TrackedTasks.Add(userContext.SyncState.NextSyncResponse); var resp = await userContext.SyncState.NextSyncResponse; var sr = await resp.Content.ReadFromJsonAsync<JsonObject>(); if (sr!.ContainsKey("error")) throw sr.Deserialize<MatrixException>()!; @@ -123,7 +127,7 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi do { if (userContext.SyncState is null) throw new NullReferenceException("syncState is null!"); // if (userContext.SyncState.NextSyncResponse is null) throw new NullReferenceException("NextSyncResponse is null"); - + //check if upstream has responded, if so, return upstream response // if (userContext.SyncState.NextSyncResponse is { IsCompleted: true } syncResponse) { // var resp = await syncResponse; @@ -146,7 +150,7 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi } // await Task.Delay(Math.Clamp(timeout, 25, 250)); //wait 25-250ms between checks - await Task.Delay(Math.Clamp(userContextService.SessionCount * 10 ,25, 500)); + await Task.Delay(Math.Clamp(userContextService.SessionCount * 10, 25, 500)); } while (_syncElapsed.ElapsedMilliseconds < timeout + 500); //... while we haven't gone >500ms over expected timeout //we didn't get a response, send a bogus response @@ -155,10 +159,11 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi new()); } - - private async Task EnqueuePreloadData(SyncState syncState) { - await EnqueuePreloadAccountData(syncState); - await EnqueuePreloadRooms(syncState); + private static async Task EnqueuePreloadData(SyncState syncState) { + new Thread(async () => { + await EnqueuePreloadAccountData(syncState); + await EnqueuePreloadRooms(syncState); + }).Start(); } private static List<string> CommonAccountDataKeys = new() { @@ -179,8 +184,9 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi "m.secret_storage.default_key", "gay.rory.mxapiextensions.userconfig" }; + //enqueue common account data - private async Task EnqueuePreloadAccountData(SyncState syncState) { + private static async Task EnqueuePreloadAccountData(SyncState syncState) { var syncMsg = new SyncResponse() { AccountData = new() { Events = new() @@ -193,22 +199,23 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi RawContent = await syncState.Homeserver.GetAccountDataAsync<JsonObject>(key) }); } - catch {} + catch { } } + syncState.SyncQueue.Enqueue(syncMsg); } - private async Task EnqueuePreloadRooms(SyncState syncState) { + private static async Task EnqueuePreloadRooms(SyncState syncState) { //get the users's rooms var rooms = await syncState.Homeserver.GetJoinedRooms(); - + //get the user's DM rooms var mDirectContent = await syncState.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); var dmRooms = mDirectContent.SelectMany(pair => pair.Value); //get our own homeserver's server_name var ownHs = syncState.Homeserver.WhoAmI!.UserId!.Split(':')[1]; - + //order rooms by expected state size, since large rooms take a long time to return rooms = rooms.OrderBy(x => { if (dmRooms.Contains(x.RoomId)) return -1; @@ -217,20 +224,25 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi if (HomeserverWeightEstimation.EstimatedSize.ContainsKey(parts[1])) return HomeserverWeightEstimation.EstimatedSize[parts[1]] + parts[0].Length; return 5000; }).ToList(); - + + foreach (var room in rooms) { + new Thread(async () => await EnqueueRoomData(syncState, room)).Start(); + } + //start all fetch tasks - var roomDataTasks = rooms.Select(room => EnqueueRoomData(syncState, room)).ToList(); - logger.LogInformation("Preloading data for {} rooms on {} ({})", roomDataTasks.Count, syncState.Homeserver.ServerName, syncState.Homeserver.AccessToken); + // var roomDataTasks = rooms.Select(room => EnqueueRoomData(syncState, room)).ToList(); + // logger.LogInformation("Preloading data for {} rooms on {} ({})", roomDataTasks.Count, syncState.Homeserver.ServerName, syncState.Homeserver.AccessToken); //wait for all of them to finish - await Task.WhenAll(roomDataTasks); + // TrackedTasks.AddRange(roomDataTasks); + // await Task.WhenAll(roomDataTasks); } private static readonly SemaphoreSlim _roomDataSemaphore = new(4, 4); - private async Task EnqueueRoomData(SyncState syncState, GenericRoom room) { + private static async Task EnqueueRoomData(SyncState syncState, GenericRoom room) { //limit concurrent requests, to not overload upstream - await _roomDataSemaphore.WaitAsync(); + // await _roomDataSemaphore.WaitAsync(); //get the room's state var roomState = room.GetFullStateAsync(); //get the room's timeline, reversed @@ -277,6 +289,6 @@ public class SyncController(ILogger<SyncController> logger, MxApiExtensionsConfi //finally, actually put the response in queue syncState.SyncQueue.Enqueue(syncResponse); - _roomDataSemaphore.Release(); + // _roomDataSemaphore.Release(); } } \ No newline at end of file diff --git a/MxApiExtensions/Controllers/Extensions/DebugController.cs b/MxApiExtensions/Controllers/Extensions/DebugController.cs index ae9ecc5..0a54481 100644 --- a/MxApiExtensions/Controllers/Extensions/DebugController.cs +++ b/MxApiExtensions/Controllers/Extensions/DebugController.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi.Extensions; using MxApiExtensions.Classes.LibMatrix; using MxApiExtensions.Services; @@ -15,6 +16,7 @@ public class DebugController(ILogger<ProxyConfigurationController> logger, MxApi [HttpGet("debug")] public async Task<object?> GetDebug() { +#if !DEBUG var user = await userContextService.GetCurrentUserContext(); var mxid = user.Homeserver.UserId; if(!config.Admins.Contains(mxid)) { @@ -29,8 +31,19 @@ public class DebugController(ILogger<ProxyConfigurationController> logger, MxApi await Response.CompleteAsync(); return null; } - _logger.LogInformation("Got debug request for {user}", mxid); - return UserContextService.UserContextStore; +#endif + + return new { + syncControllerTasks = SyncController.TrackedTasks.Select(t => new { + t?.Id, + t?.IsCompleted, + t?.IsCompletedSuccessfully, + t?.IsCanceled, + t?.IsFaulted, + Status = t?.Status.GetDisplayName() + }), + UserContextService.UserContextStore + }; } -} +} \ No newline at end of file diff --git a/MxApiExtensions/Controllers/Other/GenericProxyController.cs b/MxApiExtensions/Controllers/Other/GenericProxyController.cs index bae07c0..36ceab7 100644 --- a/MxApiExtensions/Controllers/Other/GenericProxyController.cs +++ b/MxApiExtensions/Controllers/Other/GenericProxyController.cs @@ -6,10 +6,15 @@ using MxApiExtensions.Services; namespace MxApiExtensions.Controllers; [ApiController] -[Route("/{*_}")] +[Route("/_matrix/{*_}")] public class GenericController(ILogger<GenericController> logger, MxApiExtensionsConfiguration config, AuthenticationService authenticationService, AuthenticatedHomeserverProviderService authenticatedHomeserverProviderService) : ControllerBase { + /// <summary> + /// Direct proxy to upstream + /// </summary> + /// <param name="_">API path (unused, as Request.Path is used instead)</param> + /// <param name="access_token">Optional access token</param> [HttpGet] public async Task Proxy([FromQuery] string? access_token, string? _) { try { @@ -60,6 +65,11 @@ public class GenericController(ILogger<GenericController> logger, MxApiExtension } } + /// <summary> + /// Direct proxy to upstream + /// </summary> + /// <param name="_">API path (unused, as Request.Path is used instead)</param> + /// <param name="access_token">Optional access token</param> [HttpPost] public async Task ProxyPost([FromQuery] string? access_token, string _) { try { @@ -117,6 +127,11 @@ public class GenericController(ILogger<GenericController> logger, MxApiExtension } } + /// <summary> + /// Direct proxy to upstream + /// </summary> + /// <param name="_">API path (unused, as Request.Path is used instead)</param> + /// <param name="access_token">Optional access token</param> [HttpPut] public async Task ProxyPut([FromQuery] string? access_token, string _) { try { @@ -173,4 +188,4 @@ public class GenericController(ILogger<GenericController> logger, MxApiExtension await Response.CompleteAsync(); } } -} +} \ No newline at end of file diff --git a/MxApiExtensions/Controllers/Other/MediaProxyController.cs b/MxApiExtensions/Controllers/Other/MediaProxyController.cs index fb40aa2..d4c4ea0 100644 --- a/MxApiExtensions/Controllers/Other/MediaProxyController.cs +++ b/MxApiExtensions/Controllers/Other/MediaProxyController.cs @@ -36,25 +36,49 @@ public class MediaProxyController(ILogger<GenericController> logger, MxApiExtens var a = await authenticatedHomeserverProviderService.TryGetRemoteHomeserver(); if(a is not null) FeasibleHomeservers.Add(a); + + if (a is AuthenticatedHomeserverGeneric ahg) { + var rooms = await ahg.GetJoinedRooms(); + foreach (var room in rooms) { + var ahs = (await room.GetMembersByHomeserverAsync()).Keys.Select(x=>x.ToString()).ToList(); + foreach (var ah in ahs) { + try { + if (!FeasibleHomeservers.Any(x => x.BaseUrl == ah)) { + FeasibleHomeservers.Add(await hsProvider.GetRemoteHomeserver(ah)); + } + } + catch { } + } + } + } } FeasibleHomeservers.Add(await hsProvider.GetRemoteHomeserver(serverName)); + foreach (var homeserver in FeasibleHomeservers) { var resp = await homeserver.ClientHttpClient.GetAsync($"{Request.Path}"); if(!resp.IsSuccessStatusCode) continue; entry.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json"; entry.Data = await resp.Content.ReadAsByteArrayAsync(); + if (entry.Data is not { Length: >0 }) throw new NullReferenceException("No data received?"); break; } + if (entry.Data is not { Length: >0 }) throw new NullReferenceException("No data received from any homeserver?"); + } + else if (_mediaCache[$"{serverName}/{mediaId}"].Data is not { Length: > 0 }) { + _mediaCache.Remove($"{serverName}/{mediaId}"); + await ProxyMedia(_, serverName, mediaId); + return; } else entry = _mediaCache[$"{serverName}/{mediaId}"]; + if (entry.Data is null) throw new NullReferenceException("No data?"); _semaphore.Release(); Response.StatusCode = 200; Response.ContentType = entry.ContentType; await Response.StartAsync(); - await Response.Body.WriteAsync(entry.Data, 0, entry.Data.Length); + await Response.Body.WriteAsync(entry.Data.ToArray(), 0, entry.Data.Length); await Response.Body.FlushAsync(); await Response.CompleteAsync(); } |