From 7bc30d4b6735eff6a1b9a6d5545c599b9bb9f655 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Mon, 27 May 2024 18:03:32 +0100 Subject: [PATCH 1/4] make browse_rsc respect cache --- .../Interface/Controls/ControlBrowser.cs | 5 +- .../Resources/DreamResourceManager.cs | 51 +++++++++++++++++-- OpenDreamRuntime/DreamConnection.cs | 21 ++++++-- OpenDreamRuntime/DreamManager.Connections.cs | 7 +++ .../Network/Messages/MsgBrowseResource.cs | 29 +++++------ .../Messages/MsgBrowseResourceRequest.cs | 20 ++++++++ .../Messages/MsgBrowseResourceResponse.cs | 25 +++++++++ TestGame/code.dm | 3 ++ 8 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs create mode 100644 OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs diff --git a/OpenDreamClient/Interface/Controls/ControlBrowser.cs b/OpenDreamClient/Interface/Controls/ControlBrowser.cs index fb5f58fdb8..982b23649a 100644 --- a/OpenDreamClient/Interface/Controls/ControlBrowser.cs +++ b/OpenDreamClient/Interface/Controls/ControlBrowser.cs @@ -113,7 +113,10 @@ private void RequestHandler(IRequestHandlerContext context) { Stream stream; HttpStatusCode status; var path = new ResPath(newUri.AbsolutePath); - try { + if(!_dreamResource.EnsureCacheFile(newUri.AbsolutePath)) { + stream = Stream.Null; + status = HttpStatusCode.NotFound; + } else try { stream = _resourceManager.UserData.OpenRead(_dreamResource.GetCacheFilePath(newUri.AbsolutePath)); status = HttpStatusCode.OK; } catch (FileNotFoundException) { diff --git a/OpenDreamClient/Resources/DreamResourceManager.cs b/OpenDreamClient/Resources/DreamResourceManager.cs index ef4eb7c7eb..7055d91f53 100644 --- a/OpenDreamClient/Resources/DreamResourceManager.cs +++ b/OpenDreamClient/Resources/DreamResourceManager.cs @@ -4,7 +4,6 @@ using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Network; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace OpenDreamClient.Resources { @@ -23,6 +22,7 @@ public interface IDreamResourceManager { /// The type of resource to load as. void LoadResourceAsync(int resourceId, Action onLoadCallback) where T : DreamResource; ResPath GetCacheFilePath(string filename); + public bool EnsureCacheFile(string filename, int timeoutSeconds = 5); } internal sealed class DreamResourceManager : IDreamResourceManager { @@ -38,11 +38,14 @@ internal sealed class DreamResourceManager : IDreamResourceManager { private ISawmill _sawmill = default!; + private readonly HashSet _activeBrowseRscRequests = new(); + public void Initialize() { _sawmill = Logger.GetSawmill("opendream.res"); InitCacheDirectory(); _netManager.RegisterNetMessage(RxBrowseResource); + _netManager.RegisterNetMessage(RxBrowseResourceResponse); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(RxResource); } @@ -64,7 +67,24 @@ private void InitCacheDirectory() { } private void RxBrowseResource(MsgBrowseResource message) { - CreateCacheFile(message.Filename, message.Data); + _sawmill.Debug($"Received cache check for {message.Filename}"); + if(_resourceManager.UserData.Exists(GetCacheFilePath(message.Filename))){ //TODO CHECK HASH + _sawmill.Debug($"Cache hit for {message.Filename}"); + return; + } else { + _sawmill.Debug($"Cache miss for {message.Filename}, requesting from server."); + _activeBrowseRscRequests.Add(message.Filename); + _netManager.ServerChannel?.SendMessage(new MsgBrowseResourceRequest(){ Filename = message.Filename}); + } + } + + private void RxBrowseResourceResponse(MsgBrowseResourceResponse message) { + if(_activeBrowseRscRequests.Contains(message.Filename)) { + _activeBrowseRscRequests.Remove(message.Filename); + CreateCacheFile(message.Filename, message.Data); + } else { + _sawmill.Error($"Recieved a browse_rsc response for a file we didn't ask for: {message.Filename}"); + } } private void RxResource(MsgResource message) { @@ -121,7 +141,7 @@ public void LoadResourceAsync(int resourceId, Action onLoadCallback) where _netManager.ClientSendMessage(msg); var timeout = _cfg.GetCVar(OpenDreamCVars.DownloadTimeout); - Timer.Spawn(TimeSpan.FromSeconds(timeout), () => { + Robust.Shared.Timing.Timer.Spawn(TimeSpan.FromSeconds(timeout), () => { if (_loadingResources.ContainsKey(resourceId)) { _sawmill.Warning( $"Resource id {resourceId} was requested, but is still not received {timeout} seconds later."); @@ -163,6 +183,31 @@ public ResPath CreateCacheFile(string filename, byte[] data) return new ResPath(filename); } + /// + /// Blocking check for the existence of a cached file from `browse_rsc()`. Returns true when the file is ready, or returns false if the file is not ready within timeoutSeconds. + /// + /// filepath of the cached resource (eg `./foo.png`) + /// how long to block for while waiting for the resource. Default 5 seconds. + /// + public bool EnsureCacheFile(string filename, int timeoutSeconds = 5) { + var actualPath = GetCacheFilePath(filename); + if(_resourceManager.UserData.Exists(actualPath)) { + return true; + } else { + if(_activeBrowseRscRequests.Contains(actualPath.Filename)) { + //block until the file arrives for like 5 seconds, then give up + DateTime thresholdTime = DateTime.Now.AddSeconds(timeoutSeconds); + while(!_resourceManager.UserData.Exists(actualPath) && DateTime.Now < thresholdTime) { + _netManager.ProcessPackets(); //todo this should be sleep + } + return _resourceManager.UserData.Exists(actualPath); + } else { + _sawmill.Error("Cache was ensured for a file that does not exist in cache and is not requested. Probably someobody called browse() without browse_rsc() first."); + return false; + } + } + } + private DreamResource? GetCachedResource(int resourceId) { _resourceCache.TryGetValue(resourceId, out var cached); diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 071f3a91b9..cf4bc36376 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -77,7 +77,7 @@ public DreamObjectMovable? Eye { [ViewVariables] private string _selectedStatPanel; [ViewVariables] private readonly Dictionary> _promptEvents = new(); [ViewVariables] private int _nextPromptEvent = 1; - + private readonly Dictionary _permittedBrowseRscFiles = new(); private DreamObjectMob? _mob; private DreamObjectMovable? _eye; @@ -399,10 +399,25 @@ public void BrowseResource(DreamResource resource, string filename) { var msg = new MsgBrowseResource() { Filename = filename, - Data = resource.ResourceData + DataHash = resource.ResourceData.Length //TODO: make a quick hash that can work clientside too }; + _permittedBrowseRscFiles.Add(filename, resource); + + Session?.Channel.SendMessage(msg); + } + + public void HandleBrowseResourceRequest(string filename) { + if(_permittedBrowseRscFiles.TryGetValue(filename, out var dreamResource)) { + var msg = new MsgBrowseResourceResponse() { + Filename = filename, + Data = dreamResource.ResourceData! //honestly if this is null, something mega fucked up has happened and we should error hard + }; + _permittedBrowseRscFiles.Remove(filename); + Session?.Channel.SendMessage(msg); + } else { + _sawmill.Error($"Client({Session}) requested a browse_rsc file they had not been permitted to request ({filename})."); + } - Session?.ConnectedClient.SendMessage(msg); } public void Browse(string? body, string? options) { diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 7077a15e01..ceed48dbfa 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -54,6 +54,8 @@ private void InitializeConnectionManager() { _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(RxPromptResponse); _netManager.RegisterNetMessage(); + _netManager.RegisterNetMessage(RxBrowseResourceRequest); + _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(RxTopic); _netManager.RegisterNetMessage(); @@ -224,6 +226,11 @@ private void RxAckLoadInterface(MsgAckLoadInterface message) { _playerManager.JoinGame(player); } + private void RxBrowseResourceRequest(MsgBrowseResourceRequest message) { + var connection = ConnectionForChannel(message.MsgChannel); + connection.HandleBrowseResourceRequest(message.Filename); + } + private DreamConnection ConnectionForChannel(INetChannel channel) { return _connections[_playerManager.GetSessionByChannel(channel).UserId]; } diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResource.cs b/OpenDreamShared/Network/Messages/MsgBrowseResource.cs index d0c1983338..e4a2c5d88a 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResource.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResource.cs @@ -3,24 +3,21 @@ using Robust.Shared.Network; using Robust.Shared.Serialization; -namespace OpenDreamShared.Network.Messages { - public sealed class MsgBrowseResource : NetMessage { - // TODO: Browse should be on its own channel or something. - public override MsgGroups MsgGroup => MsgGroups.EntityEvent; +namespace OpenDreamShared.Network.Messages; +public sealed class MsgBrowseResource : NetMessage { + // TODO: Browse should be on its own channel or something. + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; - public string Filename = String.Empty; - public byte[] Data = Array.Empty(); + public string Filename = String.Empty; + public int DataHash; - public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { - Filename = buffer.ReadString(); - var bytes = buffer.ReadVariableInt32(); - Data = buffer.ReadBytes(bytes); - } + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { + Filename = buffer.ReadString(); + DataHash = buffer.ReadVariableInt32(); + } - public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { - buffer.Write(Filename); - buffer.WriteVariableInt32(Data.Length); - buffer.Write(Data); - } + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { + buffer.Write(Filename); + buffer.WriteVariableInt32(DataHash); } } diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs b/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs new file mode 100644 index 0000000000..654e2b8393 --- /dev/null +++ b/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs @@ -0,0 +1,20 @@ +using System; +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace OpenDreamShared.Network.Messages; +public sealed class MsgBrowseResourceRequest : NetMessage { + // TODO: Browse should be on its own channel or something. + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public string Filename = String.Empty; + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { + Filename = buffer.ReadString(); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { + buffer.Write(Filename); + } +} diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs b/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs new file mode 100644 index 0000000000..5279f7884a --- /dev/null +++ b/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs @@ -0,0 +1,25 @@ +using System; +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace OpenDreamShared.Network.Messages; +public sealed class MsgBrowseResourceResponse : NetMessage { + // TODO: Browse should be on its own channel or something. + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public string Filename = String.Empty; + public byte[] Data = Array.Empty(); + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { + Filename = buffer.ReadString(); + var bytes = buffer.ReadVariableInt32(); + Data = buffer.ReadBytes(bytes); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { + buffer.Write(Filename); + buffer.WriteVariableInt32(Data.Length); + buffer.Write(Data); + } +} diff --git a/TestGame/code.dm b/TestGame/code.dm index c6f0d1c214..550c04c673 100644 --- a/TestGame/code.dm +++ b/TestGame/code.dm @@ -48,6 +48,9 @@ usr << "menus: [json_encode(winget(usr, null, "menus"))]" usr << "macros: [json_encode(winget(usr, null, "macros"))]" + verb/browse_rsc_test() + usr << browse_rsc('icons/mob.dmi', "mobicon.png") + usr << browse("

Oh look, it's you!","window=honk") verb/rotate() for(var/i in 1 to 8) From 9c8bd908a1b757348c657131edaa467ce453bab3 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Tue, 28 May 2024 11:47:59 +0100 Subject: [PATCH 2/4] lint --- OpenDreamClient/Resources/DreamResourceManager.cs | 1 - OpenDreamShared/Network/Messages/MsgBrowseResource.cs | 2 +- OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs | 2 +- OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/OpenDreamClient/Resources/DreamResourceManager.cs b/OpenDreamClient/Resources/DreamResourceManager.cs index 7055d91f53..0d71813f9c 100644 --- a/OpenDreamClient/Resources/DreamResourceManager.cs +++ b/OpenDreamClient/Resources/DreamResourceManager.cs @@ -70,7 +70,6 @@ private void RxBrowseResource(MsgBrowseResource message) { _sawmill.Debug($"Received cache check for {message.Filename}"); if(_resourceManager.UserData.Exists(GetCacheFilePath(message.Filename))){ //TODO CHECK HASH _sawmill.Debug($"Cache hit for {message.Filename}"); - return; } else { _sawmill.Debug($"Cache miss for {message.Filename}, requesting from server."); _activeBrowseRscRequests.Add(message.Filename); diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResource.cs b/OpenDreamShared/Network/Messages/MsgBrowseResource.cs index e4a2c5d88a..b76e7aa974 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResource.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResource.cs @@ -8,7 +8,7 @@ public sealed class MsgBrowseResource : NetMessage { // TODO: Browse should be on its own channel or something. public override MsgGroups MsgGroup => MsgGroups.EntityEvent; - public string Filename = String.Empty; + public string Filename = string.Empty; public int DataHash; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs b/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs index 654e2b8393..6bc171d683 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs @@ -8,7 +8,7 @@ public sealed class MsgBrowseResourceRequest : NetMessage { // TODO: Browse should be on its own channel or something. public override MsgGroups MsgGroup => MsgGroups.EntityEvent; - public string Filename = String.Empty; + public string Filename = string.Empty; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { Filename = buffer.ReadString(); diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs b/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs index 5279f7884a..eb58c717d0 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs @@ -8,8 +8,8 @@ public sealed class MsgBrowseResourceResponse : NetMessage { // TODO: Browse should be on its own channel or something. public override MsgGroups MsgGroup => MsgGroups.EntityEvent; - public string Filename = String.Empty; - public byte[] Data = Array.Empty(); + public string Filename = string.Empty; + public byte[] Data = []; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { Filename = buffer.ReadString(); From df42fdc875cfb89e3778d1fe375c90028721fba7 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Tue, 28 May 2024 11:54:06 +0100 Subject: [PATCH 3/4] more lint --- OpenDreamShared/Network/Messages/MsgBrowseResource.cs | 3 +-- OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs | 1 - OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResource.cs b/OpenDreamShared/Network/Messages/MsgBrowseResource.cs index b76e7aa974..b2ca8e2870 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResource.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResource.cs @@ -1,5 +1,4 @@ -using System; -using Lidgren.Network; +using Lidgren.Network; using Robust.Shared.Network; using Robust.Shared.Serialization; diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs b/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs index 6bc171d683..f860e5724e 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResourceRequest.cs @@ -1,4 +1,3 @@ -using System; using Lidgren.Network; using Robust.Shared.Network; using Robust.Shared.Serialization; diff --git a/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs b/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs index eb58c717d0..4f954737a0 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowseResourceResponse.cs @@ -1,4 +1,3 @@ -using System; using Lidgren.Network; using Robust.Shared.Network; using Robust.Shared.Serialization; From db78c22fbdb0725c17ba26eb06330c51fbc01ee7 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Thu, 30 May 2024 14:06:30 +0100 Subject: [PATCH 4/4] Update ControlBrowser.cs --- .../Interface/Controls/ControlBrowser.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/OpenDreamClient/Interface/Controls/ControlBrowser.cs b/OpenDreamClient/Interface/Controls/ControlBrowser.cs index 982b23649a..98c98e1800 100644 --- a/OpenDreamClient/Interface/Controls/ControlBrowser.cs +++ b/OpenDreamClient/Interface/Controls/ControlBrowser.cs @@ -116,16 +116,18 @@ private void RequestHandler(IRequestHandlerContext context) { if(!_dreamResource.EnsureCacheFile(newUri.AbsolutePath)) { stream = Stream.Null; status = HttpStatusCode.NotFound; - } else try { - stream = _resourceManager.UserData.OpenRead(_dreamResource.GetCacheFilePath(newUri.AbsolutePath)); - status = HttpStatusCode.OK; - } catch (FileNotFoundException) { - stream = Stream.Null; - status = HttpStatusCode.NotFound; - } catch (Exception e) { - _sawmill.Error($"Exception while loading file from {newUri}:\n{e}"); - stream = Stream.Null; - status = HttpStatusCode.InternalServerError; + } else { + try { + stream = _resourceManager.UserData.OpenRead(_dreamResource.GetCacheFilePath(newUri.AbsolutePath)); + status = HttpStatusCode.OK; + } catch (FileNotFoundException) { + stream = Stream.Null; + status = HttpStatusCode.NotFound; + } catch (Exception e) { + _sawmill.Error($"Exception while loading file from {newUri}:\n{e}"); + stream = Stream.Null; + status = HttpStatusCode.InternalServerError; + } } if (!FileExtensionMimeTypes.TryGetValue(path.Extension, out var mimeType))