[#50] MailWindow: Add button to cancel document generation
Some checks failed
Test / Run tests (push) Has been cancelled

This commit is contained in:
2026-01-16 00:22:11 +01:00
parent 01739ba42e
commit a90be2644d
5 changed files with 110 additions and 37 deletions

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Elwig.Helpers.Printing; using Elwig.Helpers.Printing;
using MimeKit; using MimeKit;
using System.Threading;
namespace Elwig.Documents { namespace Elwig.Documents {
public abstract partial class Document : IDisposable { public abstract partial class Document : IDisposable {
@@ -98,7 +99,7 @@ namespace Elwig.Documents {
return await Html.CompileRenderAsync(name, this); ; return await Html.CompileRenderAsync(name, this); ;
} }
public async Task Generate(IProgress<double>? progress = null) { public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
if (_pdfFile != null) if (_pdfFile != null)
return; return;
progress?.Report(0.0); progress?.Report(0.0);
@@ -108,36 +109,50 @@ namespace Elwig.Documents {
var pdf = new TempFile("pdf"); var pdf = new TempFile("pdf");
var tmpHtmls = new List<TempFile>(); var tmpHtmls = new List<TempFile>();
var tmpFiles = new List<string>(); var tmpFiles = new List<string>();
var n = m.Documents.Count(); try {
int i = 0; var n = m.Documents.Count();
foreach (var doc in m.Documents) { int i = 0;
if (doc is PdfDocument) { foreach (var doc in m.Documents) {
tmpFiles.Add(doc.PdfPath!); if (doc is PdfDocument) {
continue; tmpFiles.Add(doc.PdfPath!);
continue;
}
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
}
progress?.Report(GenerationProportion * 100);
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, IsDoublePaged, cancelToken, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} finally {
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
} }
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
} }
progress?.Report(GenerationProportion * 100);
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, IsDoublePaged, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
}
_pdfFile = pdf;
} else { } else {
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var pdf = new TempFile("pdf"); var pdf = new TempFile("pdf");
using (var tmpHtml = new TempFile("html")) { try {
using var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8); await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
progress?.Report(50.0); progress?.Report(50.0);
var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, IsDoublePaged); var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, IsDoublePaged, cancelToken);
TotalPages = pages.Pages; TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} }
_pdfFile = pdf;
} }
progress?.Report(100.0); progress?.Report(100.0);
} }

View File

@@ -1,15 +1,16 @@
using System.Threading.Tasks;
using Elwig.Windows; using Elwig.Windows;
using System.Diagnostics; using PdfiumViewer;
using System; using System;
using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Drawing.Printing;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using PdfiumViewer; using System.Text.RegularExpressions;
using System.Drawing.Printing; using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Printing { namespace Elwig.Helpers.Printing {
public static class Pdf { public static class Pdf {
@@ -46,24 +47,45 @@ namespace Elwig.Helpers.Printing {
return Task.CompletedTask; return Task.CompletedTask;
} }
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) { public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
return await Convert([htmlPath], pdfPath, doublePaged, progress); return await Convert([htmlPath], pdfPath, doublePaged, cancelToken, progress);
} }
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) { public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet"); if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet");
progress?.Report(0.0); progress?.Report(0.0);
using var client = new TcpClient("127.0.0.1", 30983); using var client = new TcpClient("127.0.0.1", 30983);
using var stream = client.GetStream(); using var stream = client.GetStream();
string cnxId;
using var reader = new StreamReader(stream);
var first = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
if (first.StartsWith("id:")) {
cnxId = first[3..].Trim();
} else {
throw new IOException("Invalid response from WinziPrint");
}
await stream.WriteAsync(Utils.UTF8.GetBytes( await stream.WriteAsync(Utils.UTF8.GetBytes(
"-e utf-8;-p;" + (doublePaged ? "-2;" : "") + "-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
$"{string.Join(';', htmlPath)};{pdfPath}" + $"{string.Join(';', htmlPath)};{pdfPath}" +
"\r\n")); "\r\n"));
using var reader = new StreamReader(stream); bool cancelled = false;
while (true) { while (true) {
if (!cancelled && (cancelToken?.IsCancellationRequested ?? false)) {
try {
using var cancelClient = new TcpClient("127.0.0.1", 30983);
using var cancelStream = cancelClient.GetStream();
using var cancelReader = new StreamReader(cancelStream);
await cancelReader.ReadLineAsync();
await cancelStream.WriteAsync(Utils.UTF8.GetBytes($"cancel;{cnxId}\r\n"));
} catch { }
cancelled = true;
}
var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint"); var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
if (line.StartsWith("error:")) { if (line.StartsWith("error:")) {
throw new IOException($"WinziPrint: {line[6..].Trim()}"); var msg = line[6..].Trim();
if (msg == "aborted")
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
throw new IOException($"WinziPrint: {msg}");
} else if (line.StartsWith("progress:")) { } else if (line.StartsWith("progress:")) {
var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray(); var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray();
progress?.Report(100.0 * parts[0] / parts[1]); progress?.Report(100.0 * parts[0] / parts[1]);

View File

@@ -302,6 +302,9 @@
<Button x:Name="GenerateButton" Content="Generieren" <Button x:Name="GenerateButton" Content="Generieren"
Grid.Row="0" Grid.Column="0" FontSize="14" Grid.Row="0" Grid.Column="0" FontSize="14"
Click="GenerateButton_Click"/> Click="GenerateButton_Click"/>
<Button x:Name="AbortButton" Content="Abbrechen" Visibility="Hidden" IsEnabled="False"
Grid.Row="0" Grid.Column="0" FontSize="14"
Click="AbortButton_Click"/>
<ProgressBar x:Name="ProgressBar" <ProgressBar x:Name="ProgressBar"
Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/> Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/>

View File

@@ -13,6 +13,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -61,6 +62,8 @@ namespace Elwig.Windows {
protected Dictionary<Member, List<Document>>? PrintMemberDocuments; protected Dictionary<Member, List<Document>>? PrintMemberDocuments;
protected Dictionary<Member, List<Document>>? EmailDocuments; protected Dictionary<Member, List<Document>>? EmailDocuments;
private CancellationTokenSource? CancelGeneration;
public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow)); public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow));
public int PostalAllCount { public int PostalAllCount {
get => (int)GetValue(PostalAllCountProperty); get => (int)GetValue(PostalAllCountProperty);
@@ -594,20 +597,32 @@ namespace Elwig.Windows {
} }
private void Window_Closed(object sender, EventArgs evt) { private void Window_Closed(object sender, EventArgs evt) {
CancelGeneration?.Dispose();
DisposeDocs(); DisposeDocs();
} }
private async void AbortButton_Click(object sender, RoutedEventArgs evt) {
AbortButton.IsEnabled = false;
CancelGeneration?.Cancel();
}
private async void GenerateButton_Click(object sender, RoutedEventArgs evt) { private async void GenerateButton_Click(object sender, RoutedEventArgs evt) {
LockInputs(); LockInputs();
PreviewButton.IsEnabled = false; PreviewButton.IsEnabled = false;
PrintButton.IsEnabled = false; PrintButton.IsEnabled = false;
EmailButton.IsEnabled = false; EmailButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
AbortButton.IsEnabled = true;
AbortButton.Visibility = Visibility.Visible;
GenerateButton.IsEnabled = false; GenerateButton.IsEnabled = false;
GenerateButton.Visibility = Visibility.Hidden;
DisposeDocs(); DisposeDocs();
await UpdateClientParameters(); await UpdateClientParameters();
CancelGeneration?.Dispose();
CancelGeneration = new();
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var doublePaged = DoublePagedInput.IsChecked == true; var doublePaged = DoublePagedInput.IsChecked == true;
@@ -662,6 +677,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -679,6 +697,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -740,6 +761,9 @@ namespace Elwig.Windows {
if (res != MessageBoxResult.OK) { if (res != MessageBoxResult.OK) {
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -771,7 +795,7 @@ namespace Elwig.Windows {
try { try {
foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) { foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) {
foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) { foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) {
await item2.Doc.Generate(new Progress<double>(v => { await item2.Doc.Generate(CancelGeneration.Token, new Progress<double>(v => {
ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum; ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum;
})); }));
} }
@@ -780,6 +804,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -814,7 +841,7 @@ namespace Elwig.Windows {
try { try {
var print = Document.Merge(printDocs); var print = Document.Merge(printDocs);
print.IsDoublePaged = doublePaged; print.IsDoublePaged = doublePaged;
await print.Generate(new Progress<double>(v => { await print.Generate(CancelGeneration.Token, new Progress<double>(v => {
ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum; ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum;
})); }));
PrintDocument = print; PrintDocument = print;
@@ -823,6 +850,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -834,6 +864,9 @@ namespace Elwig.Windows {
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
PreviewButton.IsEnabled = true; PreviewButton.IsEnabled = true;
PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs; PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs;

Binary file not shown.