Communication is working, showing generic error, showing simple preview

This commit is contained in:
MarcinZiabek
2022-11-19 22:26:25 +01:00
parent 320a6e221d
commit 57e68e293d
7 changed files with 304 additions and 256 deletions
+54
View File
@@ -0,0 +1,54 @@
using System;
using NUnit.Framework;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
namespace QuestPDF.UnitTests
{
public class PreviewerTests
{
[Test]
public void SendError()
{
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
//throw new Exception("Dupa!");
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.ShowInPreviewer();
}
}
}
-108
View File
@@ -1,108 +0,0 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Previewer
{
public class ExceptionDocument : IDocument
{
private Exception Exception { get; }
public ExceptionDocument(Exception exception)
{
Exception = exception;
}
public DocumentMetadata GetMetadata()
{
return DocumentMetadata.Default;
}
public void Compose(IDocumentContainer document)
{
document.Page(page =>
{
page.ContinuousSize(PageSizes.A4.Width);
page.Margin(50);
page.DefaultTextStyle(x => x.FontSize(16));
page.Foreground().PaddingTop(5).Border(10).BorderColor(Colors.Red.Medium);
page.Header()
.ShowOnce()
.PaddingBottom(5)
.Row(row =>
{
row.Spacing(15);
row.AutoItem()
.PaddingTop(15)
.Width(48)
.AspectRatio(1)
.Canvas((canvas, size) =>
{
const float iconSize = 24;
using var iconPath = SKPath.ParseSvgPathData("M23,12L20.56,14.78L20.9,18.46L17.29,19.28L15.4,22.46L12,21L8.6,22.47L6.71,19.29L3.1,18.47L3.44,14.78L1,12L3.44,9.21L3.1,5.53L6.71,4.72L8.6,1.54L12,3L15.4,1.54L17.29,4.72L20.9,5.54L20.56,9.22L23,12M20.33,12L18.5,9.89L18.74,7.1L16,6.5L14.58,4.07L12,5.18L9.42,4.07L8,6.5L5.26,7.09L5.5,9.88L3.67,12L5.5,14.1L5.26,16.9L8,17.5L9.42,19.93L12,18.81L14.58,19.92L16,17.5L18.74,16.89L18.5,14.1L20.33,12M11,15H13V17H11V15M11,7H13V13H11V7");
using var paint = new SKPaint()
{
Color = SKColors.Red,
IsAntialias = true
};
canvas.Scale(size.Width / iconSize);
canvas.DrawPath(iconPath, paint);
});
row.RelativeItem()
.Column(column =>
{
column.Item().Text("Exception").FontSize(36).FontColor(Colors.Red.Medium).Bold();
column.Item().PaddingTop(-10).Text("Don't panic! Just analyze what's happened...").FontSize(18).FontColor(Colors.Red.Medium).Bold();
});
});
page.Content().PaddingVertical(20).Column(column =>
{
var currentException = Exception;
while (currentException != null)
{
column.Item()
.PaddingTop(25)
.PaddingBottom(15)
.Padding(-10)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(x => x.FontSize(16));
text.Span(currentException.GetType().Name + ": ").Bold();
text.Span(currentException.Message);
});
foreach (var trace in currentException.StackTrace.Split('\n'))
{
column
.Item()
.ShowEntire()
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.PaddingVertical(5)
.Text(trace.Trim())
.FontSize(12);
}
currentException = currentException.InnerException;
}
});
});
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace QuestPDF.Previewer;
internal static class Helpers
{
public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
{
var json = JsonSerializer.Serialize(value);
var jsonContent = new StringContent(json, Encoding.UTF8, "application/json");
return client.PostAsync(requestUri, jsonContent, cancellationToken);
}
}
+25 -30
View File
@@ -1,4 +1,4 @@
#if NET6_0_OR_GREATER
#if NETCOREAPP3_0_OR_GREATER
using System;
using System.Collections.Generic;
@@ -11,12 +11,12 @@ namespace QuestPDF.Previewer
{
public static class Extensions
{
public static void ShowInPreviewer(this IDocument document, int port = 12500)
public static void ShowInPreviewer(this IDocument document, int port = 5000)
{
document.ShowInPreviewerAsync(port).ConfigureAwait(true).GetAwaiter().GetResult();
}
public static async Task ShowInPreviewerAsync(this IDocument document, int port = 12500)
public static async Task ShowInPreviewerAsync(this IDocument document, int port = 5000)
{
var previewerService = new PreviewerService(port);
@@ -26,39 +26,34 @@ namespace QuestPDF.Previewer
await previewerService.Connect();
await RefreshPreview();
HotReloadManager.UpdateApplicationRequested += (_, _) => RefreshPreview();
await WaitForPreviewerExit(cancellationTokenSource.Token);
//
// //HotReloadManager.UpdateApplicationRequested += (_, _) => RefreshPreview();
//
// await WaitForPreviewerExit(cancellationTokenSource.Token);
Task RefreshPreview()
{
var pictures = GetPictures();
return previewerService.RefreshPreview(pictures);
ICollection<PreviewerPicture> GetPictures()
try
{
try
{
return DocumentGenerator.GeneratePreviewerPictures(document);
}
catch (Exception exception)
{
var exceptionDocument = new ExceptionDocument(exception);
return DocumentGenerator.GeneratePreviewerPictures(exceptionDocument);
}
}
}
async Task WaitForPreviewerExit(CancellationToken cancellationToken)
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
return;
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var pictures = DocumentGenerator.GeneratePreviewerPictures(document);
return previewerService.ShowDocumentPreview(pictures);
}
catch (Exception exception)
{
return previewerService.ShowGenericError(exception);
}
}
//
// async Task WaitForPreviewerExit(CancellationToken cancellationToken)
// {
// while (true)
// {
// if (cancellationToken.IsCancellationRequested)
// return;
//
// await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
// }
// }
}
}
}
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using QuestPDF.Infrastructure;
#if NET6_0_OR_GREATER
#if NETCOREAPP3_0_OR_GREATER
namespace QuestPDF.Previewer
{
@@ -14,8 +14,8 @@ namespace QuestPDF.Previewer
{
public string Id { get; } = Guid.NewGuid().ToString("N");
public float Width { get; init; }
public float Height { get; init; }
public float Width { get; set; }
public float Height { get; set; }
}
}
}
+202 -111
View File
@@ -1,16 +1,47 @@
#if NET6_0_OR_GREATER
#if NETCOREAPP3_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using QuestPDF.Drawing;
namespace QuestPDF.Previewer
{
internal sealed class GenericError
{
public string ExceptionType { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public GenericError? InnerException { get; set; }
}
internal sealed class ShowLayoutErrorApiRequestVersion
{
public GenericError? Error { get; set; }
}
internal sealed class UpdateDocumentPreviewApiRequest
{
public ICollection<PageSnapshot> PageSnapshots { get; set; }
public class PageSnapshot
{
public string ResourceId { get; } = Guid.NewGuid().ToString("N");
public int PageNumber { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
}
internal class PreviewerService
{
private int Port { get; }
@@ -31,20 +62,80 @@ namespace QuestPDF.Previewer
};
}
public async Task ShowDocumentPreview(ICollection<PreviewerPicture> pictures)
{
using var multipartContent = new MultipartFormDataContent();
var pages = new List<UpdateDocumentPreviewApiRequest.PageSnapshot>();
foreach (var picture in pictures)
{
var page = new UpdateDocumentPreviewApiRequest.PageSnapshot
{
Width = picture.Size.Width,
Height = picture.Size.Height
};
pages.Add(page);
var pictureStream = picture.Picture.Serialize().AsStream();
multipartContent.Add(new StreamContent(pictureStream), "snapshots", page.ResourceId);
}
var request = new UpdateDocumentPreviewApiRequest
{
PageSnapshots = pages
};
var json = JsonSerializer.Serialize(request);
var jsonContent = new StringContent(json, Encoding.UTF8, "application/json");
multipartContent.Add(jsonContent, "request");
using var response = await HttpClient.PostAsync("/v1/update/preview/document", multipartContent);
response.EnsureSuccessStatusCode();
foreach (var picture in pictures)
picture.Picture.Dispose();
}
public async Task ShowGenericError(Exception exception)
{
var payload = new ShowLayoutErrorApiRequestVersion
{
Error = MapException(exception)
};
await HttpClient.PostAsJsonAsync("/v1/update/error/generic", payload);
static GenericError? MapException(Exception? exception)
{
if (exception == null)
return null;
return new GenericError
{
ExceptionType = exception.GetType().FullName,
Message = exception.Message,
StackTrace = exception.StackTrace,
InnerException = MapException(exception.InnerException)
};
}
}
public async Task Connect()
{
var isAvailable = await IsPreviewerAvailable();
if (!isAvailable)
{
StartPreviewer();
await WaitForConnection();
//StartPreviewer();
//await WaitForConnection();
}
var previewerVersion = await GetPreviewerVersion();
CheckVersionCompatibility(previewerVersion);
//var previewerVersion = await GetPreviewerVersion();
//CheckVersionCompatibility(previewerVersion);
}
private async Task<bool> IsPreviewerAvailable()
{
try
@@ -57,107 +148,107 @@ namespace QuestPDF.Previewer
return false;
}
}
private async Task<Version> GetPreviewerVersion()
{
using var result = await HttpClient.GetAsync("/version");
return await result.Content.ReadFromJsonAsync<Version>();
}
private void StartPreviewer()
{
try
{
var process = new Process
{
StartInfo = new()
{
FileName = "questpdf-previewer",
Arguments = $"{Port}",
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
Task.Run(async () =>
{
await process.WaitForExitAsync();
process.Dispose();
OnPreviewerStopped?.Invoke();
});
}
catch
{
throw new Exception("Cannot start the QuestPDF Previewer tool. " +
"Please install it by executing in terminal the following command: 'dotnet tool install --global QuestPDF.Previewer'.");
}
}
private void CheckVersionCompatibility(Version version)
{
if (version.Major == RequiredPreviewerVersionMajor && version.Minor == RequiredPreviewerVersionMinor)
return;
throw new Exception($"Previewer version is not compatible. Possible solutions: " +
$"1) Update the QuestPDF library to newer version. " +
$"2) Update the QuestPDF previewer tool using the following command: 'dotnet tool update --global QuestPDF.Previewer --version {RequiredPreviewerVersionMajor}.{RequiredPreviewerVersionMinor}'");
}
private async Task WaitForConnection()
{
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
var cancellationToken = cancellationTokenSource.Token;
while (true)
{
await Task.Delay(TimeSpan.FromMilliseconds(250));
if (cancellationToken.IsCancellationRequested)
throw new Exception($"Cannot connect to the QuestPDF Previewer tool. Please make sure that your Operating System does not block HTTP connections on port {Port}.");
var isConnected = await IsPreviewerAvailable();
if (isConnected)
break;
}
}
public async Task RefreshPreview(ICollection<PreviewerPicture> pictures)
{
using var multipartContent = new MultipartFormDataContent();
var pages = new List<PreviewerRefreshCommand.Page>();
foreach (var picture in pictures)
{
var page = new PreviewerRefreshCommand.Page
{
Width = picture.Size.Width,
Height = picture.Size.Height
};
pages.Add(page);
var pictureStream = picture.Picture.Serialize().AsStream();
multipartContent.Add(new StreamContent(pictureStream), page.Id, page.Id);
}
var command = new PreviewerRefreshCommand
{
Pages = pages
};
multipartContent.Add(JsonContent.Create(command), "command");
using var _ = await HttpClient.PostAsync("/update/preview", multipartContent);
foreach (var picture in pictures)
picture.Picture.Dispose();
}
//
// private async Task<Version> GetPreviewerVersion()
// {
// using var result = await HttpClient.GetAsync("/version");
// return await result.Content.ReadFromJsonAsync<Version>();
// }
//
// private void StartPreviewer()
// {
// try
// {
// var process = new Process
// {
// StartInfo = new()
// {
// FileName = "questpdf-previewer",
// Arguments = $"{Port}",
// UseShellExecute = false,
// CreateNoWindow = true
// }
// };
//
// process.Start();
//
// Task.Run(async () =>
// {
// await process.WaitForExitAsync();
// process.Dispose();
// OnPreviewerStopped?.Invoke();
// });
// }
// catch
// {
// throw new Exception("Cannot start the QuestPDF Previewer tool. " +
// "Please install it by executing in terminal the following command: 'dotnet tool install --global QuestPDF.Previewer'.");
// }
// }
//
// private void CheckVersionCompatibility(Version version)
// {
// if (version.Major == RequiredPreviewerVersionMajor && version.Minor == RequiredPreviewerVersionMinor)
// return;
//
// throw new Exception($"Previewer version is not compatible. Possible solutions: " +
// $"1) Update the QuestPDF library to newer version. " +
// $"2) Update the QuestPDF previewer tool using the following command: 'dotnet tool update --global QuestPDF.Previewer --version {RequiredPreviewerVersionMajor}.{RequiredPreviewerVersionMinor}'");
// }
//
// private async Task WaitForConnection()
// {
// using var cancellationTokenSource = new CancellationTokenSource();
// cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
//
// var cancellationToken = cancellationTokenSource.Token;
//
// while (true)
// {
// await Task.Delay(TimeSpan.FromMilliseconds(250));
//
// if (cancellationToken.IsCancellationRequested)
// throw new Exception($"Cannot connect to the QuestPDF Previewer tool. Please make sure that your Operating System does not block HTTP connections on port {Port}.");
//
// var isConnected = await IsPreviewerAvailable();
//
// if (isConnected)
// break;
// }
// }
//
// public async Task RefreshPreview(ICollection<PreviewerPicture> pictures)
// {
// using var multipartContent = new MultipartFormDataContent();
//
// var pages = new List<PreviewerRefreshCommand.Page>();
//
// foreach (var picture in pictures)
// {
// var page = new PreviewerRefreshCommand.Page
// {
// Width = picture.Size.Width,
// Height = picture.Size.Height
// };
//
// pages.Add(page);
//
// var pictureStream = picture.Picture.Serialize().AsStream();
// multipartContent.Add(new StreamContent(pictureStream), page.Id, page.Id);
// }
//
// var command = new PreviewerRefreshCommand
// {
// Pages = pages
// };
//
// multipartContent.Add(JsonContent.Create(command), "command");
//
// using var _ = await HttpClient.PostAsync("/update/preview", multipartContent);
//
// foreach (var picture in pictures)
// picture.Picture.Dispose();
// }
}
}
+1 -4
View File
@@ -18,6 +18,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Nullable>enable</Nullable>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0;</TargetFrameworks>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
@@ -69,8 +70,4 @@
<ItemGroup>
<Compile Remove="Previewer\Models\GenericError.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Previewer\Models" />
</ItemGroup>
</Project>