@@ -51,17 +51,17 @@ class Outline
51
51
public string ? pdfFooterTemplate { get ; init ; }
52
52
}
53
53
54
- public static Task Run ( BuildJsonConfig config , string configDirectory , string ? outputDirectory = null )
54
+ public static Task Run ( BuildJsonConfig config , string configDirectory , string ? outputDirectory = null , CancellationToken cancellationToken = default )
55
55
{
56
56
var outputFolder = Path . GetFullPath ( Path . Combine (
57
57
string . IsNullOrEmpty ( outputDirectory ) ? Path . Combine ( configDirectory , config . Output ?? "" ) : outputDirectory ,
58
58
config . Dest ?? "" ) ) ;
59
59
60
60
Logger . LogInfo ( $ "Searching for manifest in { outputFolder } ") ;
61
- return CreatePdf ( outputFolder ) ;
61
+ return CreatePdf ( outputFolder , cancellationToken ) ;
62
62
}
63
63
64
- public static async Task CreatePdf ( string outputFolder )
64
+ public static async Task CreatePdf ( string outputFolder , CancellationToken cancellationToken = default )
65
65
{
66
66
var stopwatch = Stopwatch . StartNew ( ) ;
67
67
var pdfTocs = GetPdfTocs ( ) . ToDictionary ( p => p . url , p => p . toc ) ;
@@ -82,7 +82,7 @@ public static async Task CreatePdf(string outputFolder)
82
82
using var app = builder . Build ( ) ;
83
83
app . UseServe ( outputFolder ) ;
84
84
app . MapGet ( "/_pdftoc/{*url}" , TocPage ) ;
85
- await app . StartAsync ( ) ;
85
+ await app . StartAsync ( cancellationToken ) ;
86
86
87
87
baseUrl = new Uri ( app . Urls . First ( ) ) ;
88
88
@@ -100,25 +100,51 @@ public static async Task CreatePdf(string outputFolder)
100
100
var headerFooterTemplateCache = new ConcurrentDictionary < string , string > ( ) ;
101
101
var headerFooterPageCache = new ConcurrentDictionary < ( string , string ) , Task < byte [ ] > > ( ) ;
102
102
103
- await AnsiConsole . Progress ( ) . StartAsync ( async progress =>
103
+ var pdfBuildTask = AnsiConsole . Progress ( ) . StartAsync ( async progress =>
104
104
{
105
- await Parallel . ForEachAsync ( pdfTocs , async ( item , _ ) =>
105
+ await Parallel . ForEachAsync ( pdfTocs , new ParallelOptions { CancellationToken = cancellationToken } , async ( item , _ ) =>
106
106
{
107
107
var ( url , toc ) = item ;
108
108
var outputName = Path . Combine ( Path . GetDirectoryName ( url ) ?? "" , toc . pdfFileName ?? Path . ChangeExtension ( Path . GetFileName ( url ) , ".pdf" ) ) ;
109
109
var task = progress . AddTask ( outputName ) ;
110
- var outputPath = Path . Combine ( outputFolder , outputName ) ;
110
+ var pdfOutputPath = Path . Combine ( outputFolder , outputName ) ;
111
111
112
112
await CreatePdf (
113
- PrintPdf , PrintHeaderFooter , task , new ( baseUrl , url ) , toc , outputFolder , outputPath ,
114
- pageNumbers => pdfPageNumbers [ url ] = pageNumbers ) ;
113
+ PrintPdf , PrintHeaderFooter , task , new ( baseUrl , url ) , toc , outputFolder , pdfOutputPath ,
114
+ pageNumbers => pdfPageNumbers [ url ] = pageNumbers ,
115
+ cancellationToken ) ;
115
116
116
117
task . Value = task . MaxValue ;
117
118
task . StopTask ( ) ;
118
119
} ) ;
119
120
} ) ;
120
121
122
+ try
123
+ {
124
+ await pdfBuildTask . WaitAsync ( cancellationToken ) ;
125
+ }
126
+ catch ( OperationCanceledException )
127
+ {
128
+ if ( ! pdfBuildTask . IsCompleted )
129
+ {
130
+ // If pdf generation task is not completed.
131
+ // Manually close playwright context/browser to immediately shutdown remaining tasks.
132
+ await context . CloseAsync ( ) ;
133
+ await browser . CloseAsync ( ) ;
134
+ try
135
+ {
136
+ await pdfBuildTask ; // Wait AnsiConsole.Progress operation completed to output logs.
137
+ }
138
+ catch
139
+ {
140
+ Logger . LogError ( $ "PDF file generation is canceled by user interaction.") ;
141
+ return ;
142
+ }
143
+ }
144
+ }
145
+
121
146
Logger . LogVerbose ( $ "PDF done in { stopwatch . Elapsed } ") ;
147
+ return ;
122
148
123
149
IEnumerable < ( string url , Outline toc ) > GetPdfTocs ( )
124
150
{
@@ -150,7 +176,7 @@ IResult TocPage(string url)
150
176
151
177
async Task < byte [ ] ? > PrintPdf ( Outline outline , Uri url )
152
178
{
153
- await pageLimiter . WaitAsync ( ) ;
179
+ await pageLimiter . WaitAsync ( cancellationToken ) ;
154
180
var page = pagePool . TryTake ( out var pooled ) ? pooled : await context . NewPageAsync ( ) ;
155
181
156
182
try
@@ -273,7 +299,7 @@ static string ExpandTemplate(string? pdfTemplate, int pageNumber, int totalPages
273
299
274
300
static async Task CreatePdf (
275
301
Func < Outline , Uri , Task < byte [ ] ? > > printPdf , Func < Outline , int , int , Page , Task < byte [ ] > > printHeaderFooter , ProgressTask task ,
276
- Uri outlineUrl , Outline outline , string outputFolder , string outputPath , Action < Dictionary < Outline , int > > updatePageNumbers )
302
+ Uri outlineUrl , Outline outline , string outputFolder , string pdfOutputPath , Action < Dictionary < Outline , int > > updatePageNumbers , CancellationToken cancellationToken )
277
303
{
278
304
var pages = GetPages ( outline ) . ToArray ( ) ;
279
305
if ( pages . Length == 0 )
@@ -284,7 +310,7 @@ static async Task CreatePdf(
284
310
// Make progress at 99% before merge PDF
285
311
task . MaxValue = pages . Length + ( pages . Length / 99.0 ) ;
286
312
287
- await Parallel . ForEachAsync ( pages , async ( item , _ ) =>
313
+ await Parallel . ForEachAsync ( pages , new ParallelOptions { CancellationToken = cancellationToken } , async ( item , _ ) =>
288
314
{
289
315
var ( url , node ) = item ;
290
316
if ( await printPdf ( outline , url ) is { } bytes )
@@ -302,6 +328,8 @@ await Parallel.ForEachAsync(pages, async (item, _) =>
302
328
303
329
foreach ( var ( url , node ) in pages )
304
330
{
331
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
332
+
305
333
if ( ! pageBytes . TryGetValue ( node , out var bytes ) )
306
334
continue ;
307
335
@@ -324,13 +352,14 @@ await Parallel.ForEachAsync(pages, async (item, _) =>
324
352
325
353
var producer = $ "docfx ({ typeof ( PdfBuilder ) . Assembly . GetCustomAttribute < AssemblyFileVersionAttribute > ( ) ? . Version } )";
326
354
327
- using var output = File . Create ( outputPath ) ;
355
+ using var output = File . Create ( pdfOutputPath ) ;
328
356
using var builder = new PdfDocumentBuilder ( output ) ;
329
357
330
358
builder . DocumentInformation = new ( ) { Producer = producer } ;
331
359
builder . Bookmarks = CreateBookmarks ( outline . items ) ;
332
360
333
361
await MergePdf ( ) ;
362
+ return ;
334
363
335
364
IEnumerable < ( Uri url , Outline node ) > GetPages ( Outline outline )
336
365
{
@@ -368,6 +397,8 @@ async Task MergePdf()
368
397
369
398
foreach ( var ( url , node ) in pages )
370
399
{
400
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
401
+
371
402
if ( ! pageBytes . TryGetValue ( node , out var bytes ) )
372
403
continue ;
373
404
@@ -387,6 +418,8 @@ async Task MergePdf()
387
418
using var document = PdfDocument . Open ( bytes ) ;
388
419
for ( var i = 1 ; i <= document . NumberOfPages ; i ++ )
389
420
{
421
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
422
+
390
423
pageNumber ++ ;
391
424
392
425
var pageBuilder = builder . AddPage ( document , i , x => CopyLink ( node , x ) ) ;
0 commit comments