diff --git a/Directory.Packages.props b/Directory.Packages.props index 62110b2326..1ba30a63dc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -113,6 +113,7 @@ + diff --git a/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs b/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs index 5af8b1c129..a28da09273 100644 --- a/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs +++ b/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs @@ -32,6 +32,7 @@ using Localization.Resources.AbpUi; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Volo.Abp.Account; +using Volo.Abp.BackgroundJobs; using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring.Database; using Volo.Abp.PermissionManagement.HttpApi; @@ -62,7 +63,8 @@ namespace VoloDocs.Web typeof(AbpPermissionManagementApplicationModule), typeof(AbpPermissionManagementHttpApiModule), typeof(AbpAspNetCoreMvcUiBasicThemeModule) - ,typeof(AbpCachingStackExchangeRedisModule) + ,typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpBackgroundJobsModule) )] public class VoloDocsWebModule : AbpModule { @@ -72,6 +74,11 @@ namespace VoloDocs.Web { options.AddAssemblyResource(typeof(DocsResource), typeof(VoloDocsWebModule).Assembly); }); + + PreConfigure(options => + { + options.ApplicationName = context.Services.GetApplicationName()!; + }); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/modules/docs/src/Volo.Docs.Admin.Application/BackgroundJobs/DocumentPdfGenerateJob.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/BackgroundJobs/DocumentPdfGenerateJob.cs similarity index 100% rename from modules/docs/src/Volo.Docs.Admin.Application/BackgroundJobs/DocumentPdfGenerateJob.cs rename to modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/BackgroundJobs/DocumentPdfGenerateJob.cs diff --git a/modules/docs/src/Volo.Docs.Admin.Application/BackgroundJobs/DocumentPdfGenerateJobArgs.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/BackgroundJobs/DocumentPdfGenerateJobArgs.cs similarity index 87% rename from modules/docs/src/Volo.Docs.Admin.Application/BackgroundJobs/DocumentPdfGenerateJobArgs.cs rename to modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/BackgroundJobs/DocumentPdfGenerateJobArgs.cs index be18746f17..ce8b734f2c 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/BackgroundJobs/DocumentPdfGenerateJobArgs.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/BackgroundJobs/DocumentPdfGenerateJobArgs.cs @@ -1,4 +1,5 @@ using System; +using Volo.Abp.BackgroundJobs; namespace Volo.Docs.Admin.BackgroundJobs; diff --git a/modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfAppService.cs b/modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfAppService.cs index e06c4715ac..0cfdf48bd7 100644 --- a/modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfAppService.cs +++ b/modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfAppService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using Volo.Abp.Content; +using Volo.Abp.Http; using Volo.Docs.Projects; using Volo.Docs.Projects.Pdf; @@ -37,7 +38,7 @@ public class DocumentPdfAppService : DocsCommonAppServiceBase, IDocumentPdfAppSe if (fileStream != null) { - return new RemoteStreamContent(fileStream, fileName, "application/pdf"); + return new RemoteStreamContent(fileStream, fileName, MimeTypes.Application.Zip); } return null; diff --git a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj index e57312e16b..266983eb24 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj +++ b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj @@ -25,6 +25,7 @@ + diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/DocsProjectPdfGeneratorOptions.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/DocsProjectPdfGeneratorOptions.cs index f20a0e6a51..8b8678930d 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/DocsProjectPdfGeneratorOptions.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/DocsProjectPdfGeneratorOptions.cs @@ -30,7 +30,7 @@ public class DocsProjectPdfGeneratorOptions /// /// The function to calculate the PDF file name. - /// Default is "{project.ShortName}-{version}-{languageCode}.pdf". + /// Default is "{project.ShortName}-{version}-{languageCode}.zip". /// public Func CalculatePdfFileName { get; set; } @@ -108,6 +108,6 @@ public class DocsProjectPdfGeneratorOptions width: 100%; }"; - CalculatePdfFileName = (project, version, languageCode) => $"{project.ShortName}-{version}-{languageCode}.pdf"; + CalculatePdfFileName = (project, version, languageCode) => $"{project.ShortName}-{version}-{languageCode}.zip"; } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IHtmlToPdfRenderer.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IHtmlToPdfRenderer.cs index ad1cc5105b..56fdc2f995 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IHtmlToPdfRenderer.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IHtmlToPdfRenderer.cs @@ -6,7 +6,5 @@ namespace Volo.Docs.Projects.Pdf; public interface IHtmlToPdfRenderer { - Task RenderAsync(string title, string html, List documents); - - Task MergePdfFilesAsync(List pdfFiles, string title, bool disposeStreams = true); + Task RenderAsync(string title, string html, List documents); } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs index 0a78ceb4fe..730ca898d4 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs @@ -12,7 +12,7 @@ using ITextDocument = iText.Layout.Document; namespace Volo.Docs.Projects.Pdf.IText; -public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer ,ITransientDependency +public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer { protected IOptions Options { get; } @@ -21,7 +21,7 @@ public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer ,ITransientDependency Options = options; } - public virtual Task RenderAsync(string title, string html, List documents) + public virtual Task RenderAsync(string title, string html, List documents) { var pdfStream = new MemoryStream(); var pdfWrite = new PdfWriter(pdfStream); @@ -42,7 +42,7 @@ public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer ,ITransientDependency textDocument.Close(); pdfStream.Position = 0; - return Task.FromResult(pdfStream); + return Task.FromResult(pdfStream); } public virtual async Task MergePdfFilesAsync(List pdfFiles, string title, bool disposeStreams = true) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/ProjectPdfGenerator.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/ProjectPdfGenerator.cs index 485903d79a..c93d5fe06e 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/ProjectPdfGenerator.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/ProjectPdfGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -31,7 +32,7 @@ public class ProjectPdfGenerator : IProjectPdfGenerator, ITransientDependency protected Project Project { get; set; } protected List AllPdfDocuments { get; } = []; - protected int ChunkSize { get; set; } = 10; + protected int ChunkSize { get; set; } = 60; public ProjectPdfGenerator( IDocumentSourceFactory documentStoreFactory, @@ -63,7 +64,7 @@ public class ProjectPdfGenerator : IProjectPdfGenerator, ITransientDependency await SetAllPdfDocumentsAsync(navigation.Items, project, version, languageCode); var title = Options.Value.CalculatePdfFileTitle?.Invoke(project) ?? project.Name; - var tempStreams = new List(); + var tempStreams = new List(); try { @@ -72,21 +73,28 @@ public class ProjectPdfGenerator : IProjectPdfGenerator, ITransientDependency foreach (var (chunk, index) in documentChunks.Select((chunk, index) => (chunk, index))) { + try + { - Logger.LogInformation("Processing chunk {Index}/{Total}", index + 1, documentChunks.Count); + Logger.LogInformation("Processing chunk {Index}/{Total}", index + 1, documentChunks.Count); - var chunkHtml = await BuildHtmlAsync(chunk); + var chunkHtml = await BuildHtmlAsync(chunk); - var pdfStream = await HtmlToPdfRenderer.RenderAsync($"{title} - Part {index + 1}", chunkHtml, chunk); + var pdfStream = await HtmlToPdfRenderer.RenderAsync($"{title} - Part {index + 1}", chunkHtml, chunk); - Logger.LogInformation("Chunk {Index} rendered to PDF", index + 1); + Logger.LogInformation("Chunk {Index} rendered to PDF", index + 1); - tempStreams.Add(pdfStream); + tempStreams.Add(pdfStream); + } + catch (Exception e) + { + Logger.LogError(e, "Error processing chunk {Index} for project {ProjectName}", index + 1, project.Name); + } } Logger.LogInformation("All chunks processed, merging PDF files"); - using var mergedPdfStream = await MergePdfFilesAsync(tempStreams, title, disposeStreams: true); + var mergedPdfStream = await MergePdfFilesAsync(tempStreams, title, disposeStreams: true); await ProjectPdfFileStore.SetAsync(project, version, languageCode, mergedPdfStream); } catch(Exception e) @@ -174,15 +182,41 @@ public class ProjectPdfGenerator : IProjectPdfGenerator, ITransientDependency return contentBuilder.ToString(); } - protected virtual async Task MergePdfFilesAsync(List pdfFiles, string title, bool disposeStreams = true) + protected virtual async Task MergePdfFilesAsync(List pdfFiles, string title, bool disposeStreams = true) { if (pdfFiles.Count == 0) { throw new ArgumentException("No PDF files to merge", nameof(pdfFiles)); } - return await HtmlToPdfRenderer.MergePdfFilesAsync(pdfFiles, title, disposeStreams); + var zipStream = new MemoryStream(); + + + { + using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true); + var index = 0; + foreach (var pdfFile in pdfFiles) + { + if (pdfFile.CanSeek) + pdfFile.Position = 0; + + var entry = zipArchive.CreateEntry($"{title}_{index}.pdf", CompressionLevel.Fastest); + await using var entryStream = entry.Open(); + await pdfFile.CopyToAsync(entryStream); + + if (disposeStreams) + { + await pdfFile.DisposeAsync(); + } + + index++; + } + } + + zipStream.Position = 0; + return zipStream; } + protected virtual IDocumentToHtmlConverter GetDocumentToHtmlConverter(Project project, PdfDocument pdfDocument) { diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/Puppeteer/PuppeteerHtmlToPdfRenderer.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/Puppeteer/PuppeteerHtmlToPdfRenderer.cs new file mode 100644 index 0000000000..a47269ac97 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/Puppeteer/PuppeteerHtmlToPdfRenderer.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using PuppeteerSharp; +using PuppeteerSharp.Media; +using Volo.Abp.DependencyInjection; + +namespace Volo.Docs.Projects.Pdf.Puppeteer; + +public class PuppeteerHtmlToPdfRenderer : IHtmlToPdfRenderer, ITransientDependency +{ + public async Task RenderAsync(string title, string html, List documents) + { + await new BrowserFetcher() + { + Browser = SupportedBrowser.Chromium + }.DownloadAsync(); + + await using var browser = await PuppeteerSharp.Puppeteer.LaunchAsync(new LaunchOptions { Headless = true, Timeout = 600000, Browser = SupportedBrowser.Chromium }); + await using var page = await browser.NewPageAsync(); + + await page.SetContentAsync(html, new NavigationOptions + { + Timeout = 600000, + }); + + var pdfOptions = new PdfOptions + { + Format = PaperFormat.A4, + MarginOptions = new MarginOptions { Top = "20mm", Bottom = "20mm", Left = "15mm", Right = "15mm" }, + }; + + var stream = await page.PdfStreamAsync(pdfOptions); + await page.CloseAsync(); + await browser.CloseAsync(); + return stream; + } +} \ No newline at end of file