Async support

This commit is contained in:
Yuki 2023-10-20 00:59:28 -04:00
parent 963d757ee5
commit 99907740ca
19 changed files with 208 additions and 89 deletions

View File

@ -14,15 +14,16 @@ class GeminiMediaHandler : MediaHandler
queries = new List<string>(); queries = new List<string>();
} }
public override void Load() public override async Task Load()
{ {
Title = Content.URL.AbsolutePath; Title = Content.URL.AbsolutePath;
var reader = new StreamReader(Content.Content); var reader = new StreamReader(Content.Content);
string line; string line;
while((line = reader.ReadLine()) is not null) while((line = await reader.ReadLineAsync()) is not null)
{ {
lines.Add(line); lines.Add(line);
} }
OnLoaded();
} }
public override void Render() public override void Render()

View File

@ -14,15 +14,16 @@ class GopherMediaHandler : MediaHandler
queries = new List<string>(); queries = new List<string>();
} }
public override void Load() public override async Task Load()
{ {
Title = Content.URL.AbsolutePath; Title = Content.URL.AbsolutePath;
var reader = new StreamReader(Content.Content); var reader = new StreamReader(Content.Content);
string line; string line;
while((line = reader.ReadLine()) is not null) while((line = await reader.ReadLineAsync()) is not null)
{ {
lines.Add(line); lines.Add(line);
} }
OnLoaded();
} }
public override void Render() public override void Render()
@ -67,7 +68,7 @@ class GopherMediaHandler : MediaHandler
var url = new UriBuilder(Content.URL){ var url = new UriBuilder(Content.URL){
Host = l[2], Host = l[2],
Port = int.Parse(l[3]), Port = int.Parse(l[3]),
Path = type+l[1], Path = type.ToString()+l[1],
}.ToString(); }.ToString();
Content.CurrentTab.Load(url+"%09"+queries[querynum]); Content.CurrentTab.Load(url+"%09"+queries[querynum]);
}); });
@ -81,7 +82,7 @@ class GopherMediaHandler : MediaHandler
link = new UriBuilder(Content.URL){ link = new UriBuilder(Content.URL){
Host = l[2], Host = l[2],
Port = int.Parse(l[3]), Port = int.Parse(l[3]),
Path = type+l[1], Path = type.ToString()+l[1],
}.ToString(); }.ToString();
Gui.Link(info, link, ()=> Gui.Link(info, link, ()=>
Content.CurrentTab.Load(link)); Content.CurrentTab.Load(link));

38
Media/GzipMediaHandler.cs Normal file
View File

@ -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();
}
}

View File

@ -27,16 +27,17 @@ class ImageMediaHandler : MediaHandler
Content = content; Content = content;
} }
public override void Load() public override async Task Load()
{ {
Title = Content.URL.AbsolutePath; Title = Content.URL.AbsolutePath;
using(var memory = new MemoryStream()) using(var memory = new MemoryStream())
{ {
Content.Content.CopyTo(memory); await Content.Content.CopyToAsync(memory);
var image = Raylib.LoadImageFromMemory(MediaTypes[Content.MediaType], memory.ToArray()); var image = Raylib.LoadImageFromMemory(MediaTypes[Content.MediaType], memory.ToArray());
Texture = Raylib.LoadTextureFromImage(image); Texture = Raylib.LoadTextureFromImage(image);
Raylib.UnloadImage(image); Raylib.UnloadImage(image);
} }
OnLoaded();
} }
public override void Render() public override void Render()

View File

@ -11,16 +11,18 @@ class MagickMediaHandler : ImageMediaHandler
Content = content; Content = content;
} }
public override void Load() public override async Task Load()
{ {
Title = Content.URL.AbsolutePath; 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; magic.Format = MagickFormat.Png;
var image = Raylib.LoadImageFromMemory(".png", magic.ToByteArray()); var image = Raylib.LoadImageFromMemory(".png", magic.ToByteArray());
Texture = Raylib.LoadTextureFromImage(image); Texture = Raylib.LoadTextureFromImage(image);
Raylib.UnloadImage(image); Raylib.UnloadImage(image);
} }
OnLoaded();
} }
~MagickMediaHandler() ~MagickMediaHandler()

View File

@ -18,6 +18,7 @@ class MediaHandler
{ {
public ProtoHandler Content; public ProtoHandler Content;
public string Title; public string Title;
public bool IsLoaded;
public MediaHandler() public MediaHandler()
{ {
@ -26,9 +27,17 @@ class MediaHandler
{ {
Content = content; Content = content;
} }
public virtual void Load() public virtual Task Load()
{ {
OnLoaded();
return Task.CompletedTask;
} }
public virtual void OnLoaded()
{
IsLoaded = true;
}
public virtual void Render() public virtual void Render()
{ {
Title = "Error"; Title = "Error";

View File

@ -11,15 +11,16 @@ class PlainMediaHandler : MediaHandler
lines = new List<string>(); lines = new List<string>();
} }
public override void Load() public override async Task Load()
{ {
Title = Content.URL.AbsolutePath; Title = Content.URL.AbsolutePath;
var reader = new StreamReader(Content.Content); var reader = new StreamReader(Content.Content);
string line; string line;
while((line = reader.ReadLine()) is not null) while((line = await reader.ReadLineAsync()) is not null)
{ {
lines.Add(line); lines.Add(line);
} }
OnLoaded();
} }
public override void Render() public override void Render()

View File

@ -17,7 +17,7 @@ class AboutProtoHandler : ProtoHandler
URL = url; URL = url;
} }
public override void Load() public override Task Load()
{ {
var path = new UriBuilder(URL).Path; var path = new UriBuilder(URL).Path;
Content = new MemoryStream(new byte[]{}); Content = new MemoryStream(new byte[]{});
@ -41,8 +41,10 @@ class AboutProtoHandler : ProtoHandler
MediaType = "text/plain"; MediaType = "text/plain";
Status = "OK"; Status = "OK";
Loaded = true; OnLoaded();
return Task.CompletedTask;
} }
public override void Render() public override void Render()
{ {
var path = new UriBuilder(URL).Path; var path = new UriBuilder(URL).Path;

View File

@ -10,7 +10,7 @@ class DataProtoHandler : ProtoHandler
URL = url; URL = url;
} }
public override void Load() public override Task Load()
{ {
var data = new UriBuilder(URL).Path; var data = new UriBuilder(URL).Path;
@ -37,7 +37,8 @@ class DataProtoHandler : ProtoHandler
MediaTypeParams = dict; MediaTypeParams = dict;
Status = "OK"; Status = "OK";
Loaded = true; OnLoaded();
return Task.CompletedTask;
} }
public override void Render() public override void Render()
{ {

View File

@ -1,6 +1,7 @@
using System.Text; using System.Text;
using System.Web; using System.Web;
using HeyRed.Mime; using HeyRed.Mime;
using Microsoft.Win32.SafeHandles;
namespace Shoko; namespace Shoko;
@ -11,8 +12,10 @@ class FileProtoHandler : ProtoHandler
{ {
URL = url; 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); var file = HttpUtility.UrlDecode(URL.AbsolutePath);
@ -24,9 +27,11 @@ class FileProtoHandler : ProtoHandler
if(File.Exists(file)) if(File.Exists(file))
{ {
var stream = new FileStream(file, FileMode.Open); var fi = new FileInfo(file);
MediaType = MimeGuesser.GuessMimeType(stream); _totalBytes = fi.Length;
Content = stream; var stream = fi.OpenRead();
Content = await Download(stream);
MediaType = MimeGuesser.GuessMimeType(Content);
} }
else if(Directory.Exists(file)) else if(Directory.Exists(file))
{ {
@ -47,8 +52,7 @@ class FileProtoHandler : ProtoHandler
Content = new MemoryStream(Encoding.UTF8.GetBytes("file not found")); Content = new MemoryStream(Encoding.UTF8.GetBytes("file not found"));
Status = "not found"; Status = "not found";
} }
OnLoaded();
Loaded = true;
} }
public override void Render() public override void Render()
{ {

View File

@ -12,7 +12,7 @@ class FingerProtoHandler : ProtoHandler
URL = url; URL = url;
} }
public override void Load() public override async Task Load()
{ {
var file = URL.PathAndQuery; var file = URL.PathAndQuery;
@ -24,12 +24,11 @@ class FingerProtoHandler : ProtoHandler
var stream = tcp.GetStream(); var stream = tcp.GetStream();
stream.Write(uri); await stream.WriteAsync(uri);
Content = stream; Content = await Download(stream);
MediaType = "text/plain"; MediaType = "text/plain";
OnLoaded();
Loaded = true;
} }
public override void Render() public override void Render()
{ {

View File

@ -13,8 +13,10 @@ class FtpProtoHandler : ProtoHandler
{ {
URL = url; URL = url;
} }
long _totalBytes = -1;
public override long TotalBytes => _totalBytes;
public override void Load() public override async Task Load()
{ {
var file = URL.AbsolutePath; var file = URL.AbsolutePath;
@ -45,7 +47,7 @@ class FtpProtoHandler : ProtoHandler
conn.Connect(); conn.Connect();
MediaType = "text/plain"; // TODO: magic numbers MediaType = "text/plain";
Status = "OK"; Status = "OK";
var info = conn.GetObjectInfo(file); var info = conn.GetObjectInfo(file);
@ -55,12 +57,10 @@ class FtpProtoHandler : ProtoHandler
switch(info.Type) switch(info.Type)
{ {
case FtpObjectType.File: case FtpObjectType.File:
//Content = conn.OpenRead(file); _totalBytes = info.Size;
if(conn.DownloadBytes(out byte[] bytes, info.FullName)) var stream = conn.OpenRead(file);
{ Content = await Download(stream);
Content = new MemoryStream(bytes);
MediaType = MimeGuesser.GuessMimeType(Content); MediaType = MimeGuesser.GuessMimeType(Content);
}
break; break;
case FtpObjectType.Directory: case FtpObjectType.Directory:
MediaType = "text/gemini"; MediaType = "text/gemini";
@ -107,8 +107,7 @@ class FtpProtoHandler : ProtoHandler
Status = "not found"; Status = "not found";
} }
} }
OnLoaded();
Loaded = true;
} }
public override void Render() public override void Render()
{ {

View File

@ -12,7 +12,7 @@ class GeminiProtoHandler : ProtoHandler
URL = url; URL = url;
} }
public override void Load() public override async Task Load()
{ {
var gemini = new Gemini(URL); var gemini = new Gemini(URL);
gemini.Connect(); gemini.Connect();
@ -38,7 +38,7 @@ class GeminiProtoHandler : ProtoHandler
MediaTypeParams = dict; MediaTypeParams = dict;
} }
Content = gemini.sslStream; Content = await Download(gemini.sslStream);
} }
else else
{ {
@ -54,8 +54,7 @@ class GeminiProtoHandler : ProtoHandler
} }
Content = new MemoryStream(content); Content = new MemoryStream(content);
} }
OnLoaded();
Loaded = true;
} }
public override void Render() public override void Render()

View File

@ -12,7 +12,7 @@ class GopherProtoHandler : ProtoHandler
URL = url; URL = url;
} }
public override void Load() public override async Task Load()
{ {
var type = "0"; var type = "0";
var file = URL.PathAndQuery; var file = URL.PathAndQuery;
@ -35,15 +35,6 @@ class GopherProtoHandler : ProtoHandler
else else
type = "1"; 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) switch(type)
{ {
case "0": case "0":
@ -72,7 +63,16 @@ class GopherProtoHandler : ProtoHandler
break; 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() public override void Render()
{ {

View File

@ -1,4 +1,5 @@
using System.Reflection; using System.Reflection;
using System;
namespace Shoko; namespace Shoko;
@ -10,22 +11,26 @@ class HttpProtoHandler : ProtoHandler
{ {
URL = url; 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 BaseAddress = URL
}; };
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 shoko/" + Assembly.GetExecutingAssembly().GetName().Version.ToString()); 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; Headers = response.Content.Headers;
Content = response.Content.ReadAsStream(); _totalBytes = response.Content.Headers.ContentLength ?? -1;
MediaType = response.Content.Headers.ContentType.MediaType ?? "text/html"; MediaType = response.Content.Headers.ContentType.MediaType ?? "text/html";
MediaTypeParams = response.Content.Headers.ContentType.Parameters.Select(x => new KeyValuePair<string, string>(x.Name, x.Value)); MediaTypeParams = response.Content.Headers.ContentType.Parameters.Select(x => new KeyValuePair<string, string>(x.Name, x.Value));
Status = string.Format("{0} {1}", (int)response.StatusCode, response.StatusCode.ToString()); 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() public override void Render()
{ {

View File

@ -21,11 +21,13 @@ class ProtoHandler
public string MediaType; public string MediaType;
public Stream Content; public Stream Content;
public string Status; public string Status;
public bool Loaded = false; public bool IsLoaded = false;
public int LoadedBytes = 0; protected long _loadedBytes = 0;
public int TotalBytes = 0; public virtual long LoadedBytes => _loadedBytes;
public virtual long TotalBytes => -1;
public IEnumerable<KeyValuePair<string,IEnumerable<string>>> Headers; public IEnumerable<KeyValuePair<string,IEnumerable<string>>> Headers;
public IEnumerable<KeyValuePair<string,string>> MediaTypeParams; public IEnumerable<KeyValuePair<string,string>> MediaTypeParams;
public MediaHandler Media;
public ProtoHandler() public ProtoHandler()
{ {
@ -34,11 +36,40 @@ class ProtoHandler
{ {
URL = url; URL = url;
} }
public virtual void Load() public virtual Task Load()
{ {
Content = new MemoryStream(Encoding.UTF8.GetBytes("error: no handler for this scheme")); Content = new MemoryStream(Encoding.UTF8.GetBytes("error: no handler for this scheme"));
MediaType = "text/plain"; MediaType = "text/plain";
Loaded = true; OnLoaded();
return Task.CompletedTask;
}
public async Task<MemoryStream> Download(Stream stream, Action<long> 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<MemoryStream> 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() public virtual void Render()

View File

@ -1,4 +1,5 @@
using System.Reflection; using System.Reflection;
using System.Text;
using System.Web; using System.Web;
using HeyRed.Mime; using HeyRed.Mime;
@ -12,21 +13,22 @@ class ResProtoHandler : ProtoHandler
URL = url; 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"; Status = "OK";
Loaded = true;
try try
{ {
Content = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); Content = await Download(Assembly.GetExecutingAssembly().GetManifestResourceStream(path));
MediaType = MimeGuesser.GuessMimeType(Content); MediaType = MimeGuesser.GuessMimeType(Content);
} }
catch catch
{ {
Content = new MemoryStream(Encoding.UTF8.GetBytes("resource not found"));
MediaType = "text/plain";
Status = "not found"; Status = "not found";
Loaded = false;
} }
OnLoaded();
} }
public override void Render() public override void Render()
{ {

View File

@ -12,7 +12,7 @@ class SpartanProtoHandler : ProtoHandler
URL = url; URL = url;
} }
public override void Load() public override async Task Load()
{ {
var file = URL.AbsolutePath; var file = URL.AbsolutePath;
@ -28,9 +28,9 @@ class SpartanProtoHandler : ProtoHandler
var tcp = new TcpClient(URL.Host, URL.Port < 0 ? 300 : URL.Port); var tcp = new TcpClient(URL.Host, URL.Port < 0 ? 300 : URL.Port);
var stream = tcp.GetStream(); var stream = tcp.GetStream();
stream.Write(uri); await stream.WriteAsync(uri);
if(query.Length > 0) if(query.Length > 0)
stream.Write(query); await stream.WriteAsync(query);
var reader = new StreamReader(stream); var reader = new StreamReader(stream);
var header = reader.ReadLine(); var header = reader.ReadLine();
@ -56,7 +56,7 @@ class SpartanProtoHandler : ProtoHandler
MediaTypeParams = dict; MediaTypeParams = dict;
} }
Content = stream; Content = await Download(stream);
} }
else else
{ {
@ -72,7 +72,7 @@ class SpartanProtoHandler : ProtoHandler
} }
Content = new MemoryStream(content); Content = new MemoryStream(content);
} }
Loaded = true; OnLoaded();
} }
public override void Render() public override void Render()

50
Tab.cs
View File

@ -1,3 +1,5 @@
using System.Numerics;
using FluentFTP.Helpers;
using ImGuiNET; using ImGuiNET;
namespace Shoko; namespace Shoko;
@ -5,7 +7,6 @@ namespace Shoko;
class Tab class Tab
{ {
ProtoHandler Handler; ProtoHandler Handler;
MediaHandler Document;
public bool IsOpen = true; public bool IsOpen = true;
Exception Error = null; Exception Error = null;
Stack<Uri> History; Stack<Uri> History;
@ -19,7 +20,7 @@ class Tab
txtURL = url; txtURL = url;
} }
public void Load(string url) public async Task Load(string url)
{ {
Error = null; Error = null;
ImGui.SetScrollX(0); ImGui.SetScrollX(0);
@ -28,11 +29,9 @@ class Tab
{ {
Handler = ProtoHandler.GetHandler(url); Handler = ProtoHandler.GetHandler(url);
Handler.CurrentTab = this; Handler.CurrentTab = this;
Handler.Load();
txtURL = Handler.URL.ToString(); txtURL = Handler.URL.ToString();
History.Push(Handler.URL); History.Push(Handler.URL);
Document = MediaHandler.GetHandler(Handler); await Handler.Load();
Document.Load();
} }
catch(Exception ex) catch(Exception ex)
{ {
@ -52,9 +51,9 @@ class Tab
public void Render() public void Render()
{ {
var title = txtURL; 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, ()=> Gui.Window(title+"###"+GetHashCode().ToString(), ref IsOpen, ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.HorizontalScrollbar, ()=>
{ {
@ -64,10 +63,13 @@ class Tab
}); });
if(Error is null) if(Error is null)
{
if(Handler.IsLoaded)
{ {
Handler.MenuBar(); Handler.MenuBar();
if(Handler.Loaded) if(Handler.Media.IsLoaded)
Document.MenuBar(); Handler.Media.MenuBar();
}
} }
Gui.Button("<<", ()=>{ Gui.Button("<<", ()=>{
@ -79,20 +81,42 @@ class Tab
Gui.Button("Go", ()=>{ Gui.Button("Go", ()=>{
Load(txtURL); Load(txtURL);
}); });
if(!Handler.Loaded && Handler.TotalBytes != 0) if(!Handler.IsLoaded)
ImGui.ProgressBar(Handler.LoadedBytes / Handler.TotalBytes); {
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) if(Error is not null)
{ {
ImGui.Text("error: can't load page"); ImGui.Text("error: can't load page");
ImGui.Text(Error.Message); ImGui.Text(Error.Message);
ImGui.Text(Error.StackTrace);
} }
else else
{ {
if(Handler.Loaded) try
Document.Render(); {
if(Handler.IsLoaded)
{
if(Handler.Media.IsLoaded)
Handler.Media.Render();
Handler.Render(); Handler.Render();
} }
}
catch(Exception ex)
{
ImGui.Text("error: can't render page");
ImGui.Text(ex.Message);
ImGui.Text(ex.StackTrace);
}
}
}); });
} }
} }