From 57e68e293d1cf13138aa8decf16291b80acb71ce Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Sat, 19 Nov 2022 22:26:25 +0100 Subject: [PATCH] Communication is working, showing generic error, showing simple preview --- QuestPDF.UnitTests/PreviewerTests.cs | 54 +++ QuestPDF/Previewer/ExceptionDocument.cs | 108 ------ QuestPDF/Previewer/Helpers.cs | 19 ++ QuestPDF/Previewer/PreviewerExtensions.cs | 55 ++- QuestPDF/Previewer/PreviewerRefreshCommand.cs | 6 +- QuestPDF/Previewer/PreviewerService.cs | 313 +++++++++++------- QuestPDF/QuestPDF.csproj | 5 +- 7 files changed, 304 insertions(+), 256 deletions(-) create mode 100644 QuestPDF.UnitTests/PreviewerTests.cs delete mode 100644 QuestPDF/Previewer/ExceptionDocument.cs create mode 100644 QuestPDF/Previewer/Helpers.cs diff --git a/QuestPDF.UnitTests/PreviewerTests.cs b/QuestPDF.UnitTests/PreviewerTests.cs new file mode 100644 index 0000000..7de770a --- /dev/null +++ b/QuestPDF.UnitTests/PreviewerTests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Previewer/ExceptionDocument.cs b/QuestPDF/Previewer/ExceptionDocument.cs deleted file mode 100644 index 8e2793e..0000000 --- a/QuestPDF/Previewer/ExceptionDocument.cs +++ /dev/null @@ -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; - } - }); - }); - } - } -} \ No newline at end of file diff --git a/QuestPDF/Previewer/Helpers.cs b/QuestPDF/Previewer/Helpers.cs new file mode 100644 index 0000000..9e2a172 --- /dev/null +++ b/QuestPDF/Previewer/Helpers.cs @@ -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 PostAsJsonAsync(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); + } +} \ No newline at end of file diff --git a/QuestPDF/Previewer/PreviewerExtensions.cs b/QuestPDF/Previewer/PreviewerExtensions.cs index 17eadbc..311e8fa 100644 --- a/QuestPDF/Previewer/PreviewerExtensions.cs +++ b/QuestPDF/Previewer/PreviewerExtensions.cs @@ -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 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); + // } + // } } } } diff --git a/QuestPDF/Previewer/PreviewerRefreshCommand.cs b/QuestPDF/Previewer/PreviewerRefreshCommand.cs index 6106aed..b0b3d35 100644 --- a/QuestPDF/Previewer/PreviewerRefreshCommand.cs +++ b/QuestPDF/Previewer/PreviewerRefreshCommand.cs @@ -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; } } } } diff --git a/QuestPDF/Previewer/PreviewerService.cs b/QuestPDF/Previewer/PreviewerService.cs index 578ec49..ecbf4f5 100644 --- a/QuestPDF/Previewer/PreviewerService.cs +++ b/QuestPDF/Previewer/PreviewerService.cs @@ -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 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 pictures) + { + using var multipartContent = new MultipartFormDataContent(); + + var pages = new List(); + + 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 IsPreviewerAvailable() { try @@ -57,107 +148,107 @@ namespace QuestPDF.Previewer return false; } } - - private async Task GetPreviewerVersion() - { - using var result = await HttpClient.GetAsync("/version"); - return await result.Content.ReadFromJsonAsync(); - } - - 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 pictures) - { - using var multipartContent = new MultipartFormDataContent(); - - var pages = new List(); - - 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 GetPreviewerVersion() + // { + // using var result = await HttpClient.GetAsync("/version"); + // return await result.Content.ReadFromJsonAsync(); + // } + // + // 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 pictures) + // { + // using var multipartContent = new MultipartFormDataContent(); + // + // var pages = new List(); + // + // 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(); + // } } } diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index 5b8b2a6..56ed38a 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -18,6 +18,7 @@ MIT enable net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0 + netcoreapp3.0; true snupkg @@ -69,8 +70,4 @@ - - - -