[#50] MailWindow: Add button to cancel document generation
Some checks failed
Test / Run tests (push) Has been cancelled
Some checks failed
Test / Run tests (push) Has been cancelled
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|
||||||
|
|||||||
@@ -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.
Reference in New Issue
Block a user