From 99907740cab98a0c4ea1f1016eb36c885994f54f Mon Sep 17 00:00:00 2001 From: juju2143 Date: Fri, 20 Oct 2023 00:59:28 -0400 Subject: [PATCH] Async support --- Media/GeminiMediaHandler.cs | 5 +-- Media/GopherMediaHandler.cs | 9 +++--- Media/GzipMediaHandler.cs | 38 ++++++++++++++++++++++ Media/ImageMediaHandler.cs | 5 +-- Media/MagickMediaHandler.cs | 6 ++-- Media/MediaHandler.cs | 11 ++++++- Media/PlainMediaHandler.cs | 5 +-- Protocols/AboutProtoHandler.cs | 6 ++-- Protocols/DataProtoHandler.cs | 5 +-- Protocols/FileProtoHandler.cs | 16 ++++++---- Protocols/FingerProtoHandler.cs | 9 +++--- Protocols/FtpProtoHandler.cs | 19 ++++++----- Protocols/GeminiProtoHandler.cs | 7 ++--- Protocols/GopherProtoHandler.cs | 24 +++++++------- Protocols/HttpProtoHandler.cs | 15 ++++++--- Protocols/ProtoHandler.cs | 41 +++++++++++++++++++++--- Protocols/ResProtoHandler.cs | 12 ++++--- Protocols/SpartanProtoHandler.cs | 10 +++--- Tab.cs | 54 +++++++++++++++++++++++--------- 19 files changed, 208 insertions(+), 89 deletions(-) create mode 100644 Media/GzipMediaHandler.cs diff --git a/Media/GeminiMediaHandler.cs b/Media/GeminiMediaHandler.cs index 5fe9053..d4ea341 100644 --- a/Media/GeminiMediaHandler.cs +++ b/Media/GeminiMediaHandler.cs @@ -14,15 +14,16 @@ class GeminiMediaHandler : MediaHandler queries = new List(); } - public override void Load() + public override async Task Load() { Title = Content.URL.AbsolutePath; var reader = new StreamReader(Content.Content); string line; - while((line = reader.ReadLine()) is not null) + while((line = await reader.ReadLineAsync()) is not null) { lines.Add(line); } + OnLoaded(); } public override void Render() diff --git a/Media/GopherMediaHandler.cs b/Media/GopherMediaHandler.cs index 027caa7..1484ae3 100644 --- a/Media/GopherMediaHandler.cs +++ b/Media/GopherMediaHandler.cs @@ -14,15 +14,16 @@ class GopherMediaHandler : MediaHandler queries = new List(); } - public override void Load() + public override async Task Load() { Title = Content.URL.AbsolutePath; var reader = new StreamReader(Content.Content); string line; - while((line = reader.ReadLine()) is not null) + while((line = await reader.ReadLineAsync()) is not null) { lines.Add(line); } + OnLoaded(); } public override void Render() @@ -67,7 +68,7 @@ class GopherMediaHandler : MediaHandler var url = new UriBuilder(Content.URL){ Host = l[2], Port = int.Parse(l[3]), - Path = type+l[1], + Path = type.ToString()+l[1], }.ToString(); Content.CurrentTab.Load(url+"%09"+queries[querynum]); }); @@ -81,7 +82,7 @@ class GopherMediaHandler : MediaHandler link = new UriBuilder(Content.URL){ Host = l[2], Port = int.Parse(l[3]), - Path = type+l[1], + Path = type.ToString()+l[1], }.ToString(); Gui.Link(info, link, ()=> Content.CurrentTab.Load(link)); diff --git a/Media/GzipMediaHandler.cs b/Media/GzipMediaHandler.cs new file mode 100644 index 0000000..7853f4b --- /dev/null +++ b/Media/GzipMediaHandler.cs @@ -0,0 +1,38 @@ +using System.IO.Compression; +using HeyRed.Mime; + +namespace Shoko; + +[MediaType("application/gzip")] +class GzipMediaHandler : MediaHandler +{ + MediaHandler newHandler; + public GzipMediaHandler(ProtoHandler content) + { + Content = content; + } + + public override async Task Load() + { + var stream = new GZipStream(Content.Content, CompressionMode.Decompress); + var mem = new MemoryStream(); + await stream.CopyToAsync(mem); + mem.Position = 0; + Content.MediaType = MimeGuesser.GuessMimeType(mem); + Content.Content = mem; + + newHandler = GetHandler(Content); + await newHandler.Load(); + OnLoaded(); + } + + public override void Render() + { + newHandler.Render(); + } + + public override void MenuBar() + { + newHandler.MenuBar(); + } +} \ No newline at end of file diff --git a/Media/ImageMediaHandler.cs b/Media/ImageMediaHandler.cs index e952262..7e96878 100644 --- a/Media/ImageMediaHandler.cs +++ b/Media/ImageMediaHandler.cs @@ -27,16 +27,17 @@ class ImageMediaHandler : MediaHandler Content = content; } - public override void Load() + public override async Task Load() { Title = Content.URL.AbsolutePath; using(var memory = new MemoryStream()) { - Content.Content.CopyTo(memory); + await Content.Content.CopyToAsync(memory); var image = Raylib.LoadImageFromMemory(MediaTypes[Content.MediaType], memory.ToArray()); Texture = Raylib.LoadTextureFromImage(image); Raylib.UnloadImage(image); } + OnLoaded(); } public override void Render() diff --git a/Media/MagickMediaHandler.cs b/Media/MagickMediaHandler.cs index 193ddb3..c545b39 100644 --- a/Media/MagickMediaHandler.cs +++ b/Media/MagickMediaHandler.cs @@ -11,16 +11,18 @@ class MagickMediaHandler : ImageMediaHandler Content = content; } - public override void Load() + public override async Task Load() { Title = Content.URL.AbsolutePath; - using(var magic = new MagickImage(Content.Content)) + using(var magic = new MagickImage()) { + await magic.ReadAsync(Content.Content); magic.Format = MagickFormat.Png; var image = Raylib.LoadImageFromMemory(".png", magic.ToByteArray()); Texture = Raylib.LoadTextureFromImage(image); Raylib.UnloadImage(image); } + OnLoaded(); } ~MagickMediaHandler() diff --git a/Media/MediaHandler.cs b/Media/MediaHandler.cs index 8c43667..52fa921 100644 --- a/Media/MediaHandler.cs +++ b/Media/MediaHandler.cs @@ -18,6 +18,7 @@ class MediaHandler { public ProtoHandler Content; public string Title; + public bool IsLoaded; public MediaHandler() { @@ -26,9 +27,17 @@ class MediaHandler { Content = content; } - public virtual void Load() + public virtual Task Load() { + OnLoaded(); + return Task.CompletedTask; } + + public virtual void OnLoaded() + { + IsLoaded = true; + } + public virtual void Render() { Title = "Error"; diff --git a/Media/PlainMediaHandler.cs b/Media/PlainMediaHandler.cs index 5f5701c..0b5f5b6 100644 --- a/Media/PlainMediaHandler.cs +++ b/Media/PlainMediaHandler.cs @@ -11,15 +11,16 @@ class PlainMediaHandler : MediaHandler lines = new List(); } - public override void Load() + public override async Task Load() { Title = Content.URL.AbsolutePath; var reader = new StreamReader(Content.Content); string line; - while((line = reader.ReadLine()) is not null) + while((line = await reader.ReadLineAsync()) is not null) { lines.Add(line); } + OnLoaded(); } public override void Render() diff --git a/Protocols/AboutProtoHandler.cs b/Protocols/AboutProtoHandler.cs index 919438d..e357266 100644 --- a/Protocols/AboutProtoHandler.cs +++ b/Protocols/AboutProtoHandler.cs @@ -17,7 +17,7 @@ class AboutProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override Task Load() { var path = new UriBuilder(URL).Path; Content = new MemoryStream(new byte[]{}); @@ -41,8 +41,10 @@ class AboutProtoHandler : ProtoHandler MediaType = "text/plain"; Status = "OK"; - Loaded = true; + OnLoaded(); + return Task.CompletedTask; } + public override void Render() { var path = new UriBuilder(URL).Path; diff --git a/Protocols/DataProtoHandler.cs b/Protocols/DataProtoHandler.cs index f6e9071..e75c3af 100644 --- a/Protocols/DataProtoHandler.cs +++ b/Protocols/DataProtoHandler.cs @@ -10,7 +10,7 @@ class DataProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override Task Load() { var data = new UriBuilder(URL).Path; @@ -37,7 +37,8 @@ class DataProtoHandler : ProtoHandler MediaTypeParams = dict; Status = "OK"; - Loaded = true; + OnLoaded(); + return Task.CompletedTask; } public override void Render() { diff --git a/Protocols/FileProtoHandler.cs b/Protocols/FileProtoHandler.cs index 75945e3..4cbb54d 100644 --- a/Protocols/FileProtoHandler.cs +++ b/Protocols/FileProtoHandler.cs @@ -1,6 +1,7 @@ using System.Text; using System.Web; using HeyRed.Mime; +using Microsoft.Win32.SafeHandles; namespace Shoko; @@ -11,8 +12,10 @@ class FileProtoHandler : ProtoHandler { URL = url; } + long _totalBytes = -1; + public override long TotalBytes => _totalBytes; - public override void Load() + public override async Task Load() { var file = HttpUtility.UrlDecode(URL.AbsolutePath); @@ -24,9 +27,11 @@ class FileProtoHandler : ProtoHandler if(File.Exists(file)) { - var stream = new FileStream(file, FileMode.Open); - MediaType = MimeGuesser.GuessMimeType(stream); - Content = stream; + var fi = new FileInfo(file); + _totalBytes = fi.Length; + var stream = fi.OpenRead(); + Content = await Download(stream); + MediaType = MimeGuesser.GuessMimeType(Content); } else if(Directory.Exists(file)) { @@ -47,8 +52,7 @@ class FileProtoHandler : ProtoHandler Content = new MemoryStream(Encoding.UTF8.GetBytes("file not found")); Status = "not found"; } - - Loaded = true; + OnLoaded(); } public override void Render() { diff --git a/Protocols/FingerProtoHandler.cs b/Protocols/FingerProtoHandler.cs index 4c91e56..4a609fd 100644 --- a/Protocols/FingerProtoHandler.cs +++ b/Protocols/FingerProtoHandler.cs @@ -12,7 +12,7 @@ class FingerProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override async Task Load() { var file = URL.PathAndQuery; @@ -24,12 +24,11 @@ class FingerProtoHandler : ProtoHandler var stream = tcp.GetStream(); - stream.Write(uri); + await stream.WriteAsync(uri); - Content = stream; + Content = await Download(stream); MediaType = "text/plain"; - - Loaded = true; + OnLoaded(); } public override void Render() { diff --git a/Protocols/FtpProtoHandler.cs b/Protocols/FtpProtoHandler.cs index a723051..6cb80e8 100644 --- a/Protocols/FtpProtoHandler.cs +++ b/Protocols/FtpProtoHandler.cs @@ -13,8 +13,10 @@ class FtpProtoHandler : ProtoHandler { URL = url; } + long _totalBytes = -1; + public override long TotalBytes => _totalBytes; - public override void Load() + public override async Task Load() { var file = URL.AbsolutePath; @@ -45,7 +47,7 @@ class FtpProtoHandler : ProtoHandler conn.Connect(); - MediaType = "text/plain"; // TODO: magic numbers + MediaType = "text/plain"; Status = "OK"; var info = conn.GetObjectInfo(file); @@ -55,12 +57,10 @@ class FtpProtoHandler : ProtoHandler switch(info.Type) { case FtpObjectType.File: - //Content = conn.OpenRead(file); - if(conn.DownloadBytes(out byte[] bytes, info.FullName)) - { - Content = new MemoryStream(bytes); - MediaType = MimeGuesser.GuessMimeType(Content); - } + _totalBytes = info.Size; + var stream = conn.OpenRead(file); + Content = await Download(stream); + MediaType = MimeGuesser.GuessMimeType(Content); break; case FtpObjectType.Directory: MediaType = "text/gemini"; @@ -107,8 +107,7 @@ class FtpProtoHandler : ProtoHandler Status = "not found"; } } - - Loaded = true; + OnLoaded(); } public override void Render() { diff --git a/Protocols/GeminiProtoHandler.cs b/Protocols/GeminiProtoHandler.cs index 41f66b7..0b26a7f 100644 --- a/Protocols/GeminiProtoHandler.cs +++ b/Protocols/GeminiProtoHandler.cs @@ -12,7 +12,7 @@ class GeminiProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override async Task Load() { var gemini = new Gemini(URL); gemini.Connect(); @@ -38,7 +38,7 @@ class GeminiProtoHandler : ProtoHandler MediaTypeParams = dict; } - Content = gemini.sslStream; + Content = await Download(gemini.sslStream); } else { @@ -54,8 +54,7 @@ class GeminiProtoHandler : ProtoHandler } Content = new MemoryStream(content); } - - Loaded = true; + OnLoaded(); } public override void Render() diff --git a/Protocols/GopherProtoHandler.cs b/Protocols/GopherProtoHandler.cs index 1accd36..9c46d56 100644 --- a/Protocols/GopherProtoHandler.cs +++ b/Protocols/GopherProtoHandler.cs @@ -12,7 +12,7 @@ class GopherProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override async Task Load() { var type = "0"; var file = URL.PathAndQuery; @@ -34,16 +34,7 @@ class GopherProtoHandler : ProtoHandler } else type = "1"; - - var uri = Encoding.UTF8.GetBytes(HttpUtility.UrlDecode(string.Join("/", paths))+"\r\n"); - - var tcp = new TcpClient(URL.Host, URL.Port < 0 ? 70 : URL.Port); - - var stream = tcp.GetStream(); - - stream.Write(uri); - - Content = stream; + switch(type) { case "0": @@ -72,7 +63,16 @@ class GopherProtoHandler : ProtoHandler break; } - Loaded = true; + var uri = Encoding.UTF8.GetBytes(HttpUtility.UrlDecode(string.Join("/", paths))+"\r\n"); + + var tcp = new TcpClient(URL.Host, URL.Port < 0 ? 70 : URL.Port); + + var stream = tcp.GetStream(); + + stream.Write(uri); + + Content = await Download(stream); + OnLoaded(); } public override void Render() { diff --git a/Protocols/HttpProtoHandler.cs b/Protocols/HttpProtoHandler.cs index 33a58bc..88d94dc 100644 --- a/Protocols/HttpProtoHandler.cs +++ b/Protocols/HttpProtoHandler.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System; namespace Shoko; @@ -10,22 +11,26 @@ class HttpProtoHandler : ProtoHandler { URL = url; } + long _totalBytes = -1; + public override long TotalBytes => _totalBytes; - public override void Load() + public override async Task Load() { - HttpClient client = new(){ + var client = new HttpClient(){ BaseAddress = URL }; client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 shoko/" + Assembly.GetExecutingAssembly().GetName().Version.ToString()); - var response = client.GetAsync(URL).Result; + var response = await client.GetAsync(URL, HttpCompletionOption.ResponseHeadersRead); Headers = response.Content.Headers; - Content = response.Content.ReadAsStream(); + _totalBytes = response.Content.Headers.ContentLength ?? -1; MediaType = response.Content.Headers.ContentType.MediaType ?? "text/html"; MediaTypeParams = response.Content.Headers.ContentType.Parameters.Select(x => new KeyValuePair(x.Name, x.Value)); Status = string.Format("{0} {1}", (int)response.StatusCode, response.StatusCode.ToString()); - Loaded = true; + var download = await response.Content.ReadAsStreamAsync(); + Content = await Download(download); + OnLoaded(); } public override void Render() { diff --git a/Protocols/ProtoHandler.cs b/Protocols/ProtoHandler.cs index 22ed65e..4bcd628 100644 --- a/Protocols/ProtoHandler.cs +++ b/Protocols/ProtoHandler.cs @@ -21,11 +21,13 @@ class ProtoHandler public string MediaType; public Stream Content; public string Status; - public bool Loaded = false; - public int LoadedBytes = 0; - public int TotalBytes = 0; + public bool IsLoaded = false; + protected long _loadedBytes = 0; + public virtual long LoadedBytes => _loadedBytes; + public virtual long TotalBytes => -1; public IEnumerable>> Headers; public IEnumerable> MediaTypeParams; + public MediaHandler Media; public ProtoHandler() { @@ -34,11 +36,40 @@ class ProtoHandler { URL = url; } - public virtual void Load() + public virtual Task Load() { Content = new MemoryStream(Encoding.UTF8.GetBytes("error: no handler for this scheme")); MediaType = "text/plain"; - Loaded = true; + OnLoaded(); + return Task.CompletedTask; + } + + public async Task Download(Stream stream, Action progress) + { + var ret = new MemoryStream(); + var buf = new byte[81920]; + long totalBytes = 0; + int bytesRead; + while ((bytesRead = await stream.ReadAsync(buf, 0, buf.Length)) != 0) { + await ret.WriteAsync(buf.AsMemory(0, bytesRead)); + totalBytes += bytesRead; + progress(totalBytes); + } + ret.Position = 0; + return ret; + } + + public async Task Download(Stream stream) + { + return await Download(stream, b=>_loadedBytes=b); + } + + + public virtual void OnLoaded() + { + Media = MediaHandler.GetHandler(this); + IsLoaded = true; + Media.Load(); } public virtual void Render() diff --git a/Protocols/ResProtoHandler.cs b/Protocols/ResProtoHandler.cs index b45e483..8e046ad 100644 --- a/Protocols/ResProtoHandler.cs +++ b/Protocols/ResProtoHandler.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Text; using System.Web; using HeyRed.Mime; @@ -12,21 +13,22 @@ class ResProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override async Task Load() { - var path = HttpUtility.HtmlDecode(new UriBuilder(URL).Path); + var path = HttpUtility.HtmlDecode(URL.AbsolutePath); Status = "OK"; - Loaded = true; try { - Content = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + Content = await Download(Assembly.GetExecutingAssembly().GetManifestResourceStream(path)); MediaType = MimeGuesser.GuessMimeType(Content); } catch { + Content = new MemoryStream(Encoding.UTF8.GetBytes("resource not found")); + MediaType = "text/plain"; Status = "not found"; - Loaded = false; } + OnLoaded(); } public override void Render() { diff --git a/Protocols/SpartanProtoHandler.cs b/Protocols/SpartanProtoHandler.cs index 1f16a16..0dfc73a 100644 --- a/Protocols/SpartanProtoHandler.cs +++ b/Protocols/SpartanProtoHandler.cs @@ -12,7 +12,7 @@ class SpartanProtoHandler : ProtoHandler URL = url; } - public override void Load() + public override async Task Load() { var file = URL.AbsolutePath; @@ -28,9 +28,9 @@ class SpartanProtoHandler : ProtoHandler var tcp = new TcpClient(URL.Host, URL.Port < 0 ? 300 : URL.Port); var stream = tcp.GetStream(); - stream.Write(uri); + await stream.WriteAsync(uri); if(query.Length > 0) - stream.Write(query); + await stream.WriteAsync(query); var reader = new StreamReader(stream); var header = reader.ReadLine(); @@ -56,7 +56,7 @@ class SpartanProtoHandler : ProtoHandler MediaTypeParams = dict; } - Content = stream; + Content = await Download(stream); } else { @@ -72,7 +72,7 @@ class SpartanProtoHandler : ProtoHandler } Content = new MemoryStream(content); } - Loaded = true; + OnLoaded(); } public override void Render() diff --git a/Tab.cs b/Tab.cs index e20f52f..e3a01a7 100644 --- a/Tab.cs +++ b/Tab.cs @@ -1,3 +1,5 @@ +using System.Numerics; +using FluentFTP.Helpers; using ImGuiNET; namespace Shoko; @@ -5,7 +7,6 @@ namespace Shoko; class Tab { ProtoHandler Handler; - MediaHandler Document; public bool IsOpen = true; Exception Error = null; Stack History; @@ -19,7 +20,7 @@ class Tab txtURL = url; } - public void Load(string url) + public async Task Load(string url) { Error = null; ImGui.SetScrollX(0); @@ -28,11 +29,9 @@ class Tab { Handler = ProtoHandler.GetHandler(url); Handler.CurrentTab = this; - Handler.Load(); txtURL = Handler.URL.ToString(); History.Push(Handler.URL); - Document = MediaHandler.GetHandler(Handler); - Document.Load(); + await Handler.Load(); } catch(Exception ex) { @@ -52,9 +51,9 @@ class Tab public void Render() { var title = txtURL; - if(Document is not null) + if(Handler.Media is not null) { - title = Document.Title; + title = Handler.Media.Title; } Gui.Window(title+"###"+GetHashCode().ToString(), ref IsOpen, ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.HorizontalScrollbar, ()=> { @@ -65,9 +64,12 @@ class Tab if(Error is null) { - Handler.MenuBar(); - if(Handler.Loaded) - Document.MenuBar(); + if(Handler.IsLoaded) + { + Handler.MenuBar(); + if(Handler.Media.IsLoaded) + Handler.Media.MenuBar(); + } } Gui.Button("<<", ()=>{ @@ -79,19 +81,41 @@ class Tab Gui.Button("Go", ()=>{ Load(txtURL); }); - if(!Handler.Loaded && Handler.TotalBytes != 0) - ImGui.ProgressBar(Handler.LoadedBytes / Handler.TotalBytes); + if(!Handler.IsLoaded) + { + ImGui.Text("Loading..."); + if(Handler.TotalBytes != 0 && Handler.TotalBytes != Handler.LoadedBytes) + ImGui.ProgressBar(Handler.LoadedBytes / Handler.TotalBytes, new Vector2(200, 20), + Handler.LoadedBytes.FileSizeToString() + "/" + Handler.TotalBytes.FileSizeToString()); + } + else if(!Handler.Media.IsLoaded) + { + ImGui.Text("Rendering..."); + } }); if(Error is not null) { ImGui.Text("error: can't load page"); ImGui.Text(Error.Message); + ImGui.Text(Error.StackTrace); } else { - if(Handler.Loaded) - Document.Render(); - Handler.Render(); + try + { + if(Handler.IsLoaded) + { + if(Handler.Media.IsLoaded) + Handler.Media.Render(); + Handler.Render(); + } + } + catch(Exception ex) + { + ImGui.Text("error: can't render page"); + ImGui.Text(ex.Message); + ImGui.Text(ex.StackTrace); + } } }); }