Skip to content

Commit

Permalink
release 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
heftymouse committed Dec 25, 2023
1 parent b659cf8 commit 728c888
Show file tree
Hide file tree
Showing 101 changed files with 1,088 additions and 740 deletions.
3 changes: 3 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Privacy Policy

Phobos does not knowingly collect any kind of personal information or telemetry.
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
using System.Text;
using System.Threading.Tasks;

namespace Twin.Helpers
namespace Phobos.Helpers
{
internal class UriHelpers
{
public static string UriToString(Uri uri)
{
if (uri is null) return null;
return $"gemini://{uri.Host}{uri.PathAndQuery}";
return $"gemini://{uri.Host}{(uri.Port == 1965 ? $":${uri.Port}" : string.Empty)}{uri.PathAndQuery}";
}

public static Uri StringToUri(string uri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;

namespace Twin.Core.Gemini
namespace Phobos.Core.Models
{
public enum GeminiResponseType
{
Expand All @@ -16,5 +16,5 @@ public enum GeminiResponseType
ClientCertificateRequired = 6
}

public readonly record struct GeminiResponse(GeminiResponseType Type, int Code, string Meta, string Body);
public readonly record struct GeminiResponse(GeminiResponseType Type, int Code, string Meta, byte[] Body);
}
43 changes: 43 additions & 0 deletions Phobos.Core/Models/Gemtext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Phobos.Core.Models
{
public enum GemtextNodeType
{
Text,
Link,
Preformat,
Heading,
ListItem,
Quote
}

public abstract record GemtextNode
{
public GemtextNodeType Type { get; init; }
public string Content { get; init; }

protected GemtextNode(string content, GemtextNodeType type)
{
this.Content = content;
this.Type = type;
}
}

public record TextNode(string Content) : GemtextNode(Content, GemtextNodeType.Text);

public record LinkNode(string Content, Uri Uri) : GemtextNode(Content, GemtextNodeType.Link);

public record PreformatNode(string Content, string? AltText) : GemtextNode(Content, GemtextNodeType.Preformat);

public record HeadingNode(string Content, int Level) : GemtextNode(Content, GemtextNodeType.Heading);

public record ListItemNode(string Content) : GemtextNode(Content, GemtextNodeType.ListItem);

public record QuoteNode(string Content) : GemtextNode(Content, GemtextNodeType.Quote);
}

6 changes: 4 additions & 2 deletions Twin.Core/Models/History.cs → Phobos.Core/Models/History.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Phobos.Core.ViewModels;

namespace Twin.Core.Models
namespace Phobos.Core.Models
{
public partial class History : ObservableObject
{
Expand All @@ -17,7 +18,8 @@ public partial class History : ObservableObject

public void Add(Uri uri)
{
historyList.Add(uri);
if (historyList.Count == 0 || historyList[CurrentPage] != uri)
historyList.Add(uri);
CurrentPage = historyList.Count - 1;
CanGoBack = historyList.Count > 1;
CanGoForward = false;
Expand Down
20 changes: 20 additions & 0 deletions Phobos.Core/Models/ViewState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Phobos.Core.Models
{
public enum ViewState
{
StartPage,
Page,
UnsupportedFile,
Input,
InputSensitive,
ClientCertificate,
Error,
InternalError
}
}
10 changes: 5 additions & 5 deletions Twin.Core/Twin.Core.csproj → Phobos.Core/Phobos.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>

</Project>
155 changes: 155 additions & 0 deletions Phobos.Core/Services/GeminiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Buffers.Binary;
using System.Threading.Tasks;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Net.Sockets;
using Phobos.Core.Models;
using CommunityToolkit.HighPerformance.Buffers;
using CommunityToolkit.HighPerformance;

namespace Phobos.Core.Services
{
public class GeminiService
{
public GeminiResponse RequestPageAsync(Uri uri)
{
using TcpClient client = new TcpClient(uri.Host, uri.Port == -1 ? 1965 : uri.Port);
using SslStream sslStream = new SslStream(client.GetStream(), false, VerifyServerCert);

sslStream.AuthenticateAsClient(uri.Host, null, SslProtocols.Tls13 | SslProtocols.Tls12, true);

sslStream.Write(Encoding.UTF8.GetBytes(uri.ToString()));
sslStream.Write("\r\n"u8);
sslStream.Flush();

using ArrayPoolBufferWriter<byte> buffer = new();
while (true)
{
var span = buffer.GetSpan(4096);
int bytesRead = sslStream.Read(span);
buffer.Advance(bytesRead);
if (bytesRead == 0) break;
}

var data = buffer.WrittenSpan;

var headerEnd = data.IndexOf("\r\n"u8);
var meta = Encoding.UTF8.GetString(data.Slice(3, headerEnd - 3));
var body = data.Slice(headerEnd + 2).AsBytes().ToArray();

return new GeminiResponse((GeminiResponseType)data[0] - 48, data[1] - 48, meta, body);
}

static bool VerifyServerCert(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors errors)
{
//byte[] hash = new byte[64];
//certificate.TryGetCertHash(HashAlgorithmName.SHA256, hash, out int bytesWritten);
return true;
}

public static List<GemtextNode> ParseBody(char[] body, Uri rootHost)
{
Stopwatch stopwatch = new();
stopwatch.Start();
List<GemtextNode> result = new();

bool isPreformat = false;
StringBuilder preformatBuilder = new();
string preformatAlt = null;

bool isQuote = false;
StringBuilder quoteBuilder = new();

var lines = body.AsSpan().EnumerateLines();
foreach (var line in lines)
{
if (isQuote && line.Length > 0 && line[0] is not '>')
{
isQuote = false;
result.Add(new QuoteNode(quoteBuilder.ToString()));
}

if (line.Length == 0)
{
result.Add(new TextNode(""));
continue;
}

if (line.StartsWith("```".AsSpan()))
{
if (isPreformat)
{
isPreformat = false;
result.Add(new PreformatNode(preformatBuilder.ToString(), preformatAlt));
}
else
{
isPreformat = true;
Span<Range> ranges = new Range[2];
var num = line.SplitAny(ranges, ReadOnlySpan<char>.Empty, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
preformatBuilder = new();
preformatAlt = num == 2 ? line[ranges[1]].ToString() : null;
}
}

else if (isPreformat)
{
preformatBuilder.Append(line);
preformatBuilder.Append('\n');
}

else if (line[0] is '#')
{
var count = line.Slice(0, 3).LastIndexOf('#') + 1;
var heading = line.Slice(count).Trim().ToString();
HeadingNode node = new(heading, count);
result.Add(node);
}

else if (line.StartsWith("=>".AsSpan()))
{
var text = line.TrimStart(['=', '>']);
Span<Range> ranges = new Range[2];
var num = text.SplitAny(ranges, ReadOnlySpan<char>.Empty, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var uriString = text[ranges[0]].ToString();
var uri = uriString.Contains("://")
? new Uri(uriString)
: new Uri(rootHost, uriString);
var description = num == 2 ? text[ranges[1]].ToString() : uriString;
result.Add(new LinkNode(description, uri));
}

else if (line[0] is '*')
{
result.Add(new ListItemNode(line.Slice(1).Trim().ToString()));
}

else if (line[0] is '>')
{
if(!isQuote)
{
isQuote = true;
quoteBuilder = new();
}
var text = line.TrimStart('>').Trim();
quoteBuilder.Append(text);
quoteBuilder.Append('\n');
}

else
result.Add(new TextNode(line.ToString()));
}

stopwatch.Stop();
Debug.WriteLine($"Parsing done in {stopwatch.Elapsed.TotalSeconds} s");
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;

namespace Twin.Core.Services
namespace Phobos.Core.Services
{
public interface IDialogService
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;

namespace Twin.Core.Services
namespace Phobos.Core.Services
{
public interface IFileService
{
Expand Down
13 changes: 13 additions & 0 deletions Phobos.Core/Services/IHistoryService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Phobos.Core.Services
{
public interface IHistoryService
{

}
}
Loading

0 comments on commit 728c888

Please sign in to comment.