Profilnamen aufloesen, unbekannte Drucker ablehnen (400), SumatraPDF-ExitCode als Fehler, success:false in Fehlerantworten
This commit is contained in:
parent
93f5c72ef2
commit
b88752186d
2 changed files with 100 additions and 35 deletions
|
|
@ -23,7 +23,7 @@ public class PrintController : ControllerBase
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PDF hochladen und drucken.
|
/// PDF hochladen und drucken.
|
||||||
/// POST /api/print/upload
|
/// POST /api/print/upload
|
||||||
/// Form-Data: file, printer (opt), paperSource (opt), copies (opt)
|
/// Form-Data: file, printer (opt, Druckername oder Profilname), paperSource (opt), copies (opt)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
[Consumes("multipart/form-data")]
|
[Consumes("multipart/form-data")]
|
||||||
|
|
@ -34,11 +34,11 @@ public class PrintController : ControllerBase
|
||||||
[FromForm] int copies = 0)
|
[FromForm] int copies = 0)
|
||||||
{
|
{
|
||||||
if (file is null || file.Length == 0)
|
if (file is null || file.Length == 0)
|
||||||
return BadRequest(new { error = "Keine Datei." });
|
return BadRequest(new { success = false, error = "Keine Datei." });
|
||||||
|
|
||||||
var ext = Path.GetExtension(file.FileName).ToLower();
|
var ext = Path.GetExtension(file.FileName).ToLower();
|
||||||
if (!_options.AllowedExtensions.Contains(ext))
|
if (!_options.AllowedExtensions.Contains(ext))
|
||||||
return BadRequest(new { error = $"Dateityp '{ext}' nicht erlaubt." });
|
return BadRequest(new { success = false, error = $"Dateityp '{ext}' nicht erlaubt." });
|
||||||
|
|
||||||
Directory.CreateDirectory(_options.TempDirectory);
|
Directory.CreateDirectory(_options.TempDirectory);
|
||||||
var tempFile = Path.Combine(_options.TempDirectory, $"{Guid.NewGuid()}{ext}");
|
var tempFile = Path.Combine(_options.TempDirectory, $"{Guid.NewGuid()}{ext}");
|
||||||
|
|
@ -48,7 +48,7 @@ public class PrintController : ControllerBase
|
||||||
await using (var fs = System.IO.File.Create(tempFile))
|
await using (var fs = System.IO.File.Create(tempFile))
|
||||||
await file.CopyToAsync(fs);
|
await file.CopyToAsync(fs);
|
||||||
|
|
||||||
_printService.PrintPdf(new PrintJob
|
var r = _printService.PrintPdf(new PrintJob
|
||||||
{
|
{
|
||||||
FilePath = tempFile,
|
FilePath = tempFile,
|
||||||
MailSubject = file.FileName,
|
MailSubject = file.FileName,
|
||||||
|
|
@ -58,12 +58,16 @@ public class PrintController : ControllerBase
|
||||||
CopiesOverride = copies > 0 ? copies : null
|
CopiesOverride = copies > 0 ? copies : null
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(new { success = true, file = file.FileName, printer, paperSource });
|
return Ok(new { success = true, file = file.FileName, profile = r.Profile, printer = r.Printer, paperSource = r.PaperSource, copies = r.Copies });
|
||||||
|
}
|
||||||
|
catch (PrintConfigException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { success = false, error = ex.Message });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "WebAPI Druckfehler: {File}", file.FileName);
|
_logger.LogError(ex, "WebAPI Druckfehler: {File}", file.FileName);
|
||||||
return StatusCode(500, new { error = ex.Message });
|
return StatusCode(500, new { success = false, error = ex.Message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +79,7 @@ public class PrintController : ControllerBase
|
||||||
public async Task<IActionResult> PrintUrl([FromBody] PrintUrlRequest request)
|
public async Task<IActionResult> PrintUrl([FromBody] PrintUrlRequest request)
|
||||||
{
|
{
|
||||||
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri))
|
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri))
|
||||||
return BadRequest(new { error = "Ungültige URL." });
|
return BadRequest(new { success = false, error = "Ungültige URL." });
|
||||||
|
|
||||||
Directory.CreateDirectory(_options.TempDirectory);
|
Directory.CreateDirectory(_options.TempDirectory);
|
||||||
var tempFile = Path.Combine(_options.TempDirectory, $"{Guid.NewGuid()}.pdf");
|
var tempFile = Path.Combine(_options.TempDirectory, $"{Guid.NewGuid()}.pdf");
|
||||||
|
|
@ -85,7 +89,7 @@ public class PrintController : ControllerBase
|
||||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
||||||
await System.IO.File.WriteAllBytesAsync(tempFile, await http.GetByteArrayAsync(uri));
|
await System.IO.File.WriteAllBytesAsync(tempFile, await http.GetByteArrayAsync(uri));
|
||||||
|
|
||||||
_printService.PrintPdf(new PrintJob
|
var r = _printService.PrintPdf(new PrintJob
|
||||||
{
|
{
|
||||||
FilePath = tempFile,
|
FilePath = tempFile,
|
||||||
MailSubject = uri.Segments.LastOrDefault() ?? "download.pdf",
|
MailSubject = uri.Segments.LastOrDefault() ?? "download.pdf",
|
||||||
|
|
@ -95,12 +99,16 @@ public class PrintController : ControllerBase
|
||||||
CopiesOverride = request.Copies > 0 ? request.Copies : null
|
CopiesOverride = request.Copies > 0 ? request.Copies : null
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(new { success = true, url = request.Url, printer = request.Printer, paperSource = request.PaperSource });
|
return Ok(new { success = true, url = request.Url, profile = r.Profile, printer = r.Printer, paperSource = r.PaperSource, copies = r.Copies });
|
||||||
|
}
|
||||||
|
catch (PrintConfigException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { success = false, error = ex.Message });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "WebAPI URL-Druckfehler: {Url}", request.Url);
|
_logger.LogError(ex, "WebAPI URL-Druckfehler: {Url}", request.Url);
|
||||||
return StatusCode(500, new { error = ex.Message });
|
return StatusCode(500, new { success = false, error = ex.Message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,25 +23,31 @@ public class PrintService
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintPdf(PrintJob job)
|
/// <summary>
|
||||||
|
/// Druckt das PDF. Wirft <see cref="PrintConfigException"/> bei unbekanntem Drucker/Profil,
|
||||||
|
/// sonst Exception bei jedem Druckfehler (inkl. SumatraPDF-ExitCode ≠ 0).
|
||||||
|
/// </summary>
|
||||||
|
public PrintResult PrintPdf(PrintJob job)
|
||||||
{
|
{
|
||||||
// Profil auflösen: direkte Overrides > benanntes Profil > erstes Profil > Defaults
|
|
||||||
var profile = ResolveProfile(job);
|
|
||||||
var printerName = !string.IsNullOrEmpty(job.PrinterOverride) ? job.PrinterOverride : profile.PrinterName;
|
|
||||||
var paperSource = !string.IsNullOrEmpty(job.PaperSourceOverride) ? job.PaperSourceOverride : profile.PaperSource;
|
|
||||||
var copies = job.CopiesOverride ?? profile.Copies;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(printerName))
|
|
||||||
printerName = GetDefaultPrinter();
|
|
||||||
|
|
||||||
_logger.LogInformation("Drucke '{Subject}' → {Printer} | Fach: {Fach} | {Copies}x",
|
|
||||||
job.MailSubject, printerName, string.IsNullOrEmpty(paperSource) ? "Standard" : paperSource, copies);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < copies; i++)
|
var r = Resolve(job);
|
||||||
PrintOnce(job.FilePath, printerName, paperSource, profile.Duplex);
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Drucke '{Subject}' → {Printer} | Profil: {Profile} | Fach: {Fach} | Duplex: {Duplex} | {Copies}x",
|
||||||
|
job.MailSubject, r.Printer, string.IsNullOrEmpty(r.Profile) ? "-" : r.Profile,
|
||||||
|
string.IsNullOrEmpty(r.PaperSource) ? "Standard" : r.PaperSource, r.Duplex, r.Copies);
|
||||||
|
|
||||||
|
for (int i = 0; i < r.Copies; i++)
|
||||||
|
PrintOnce(job.FilePath, r.Printer, r.PaperSource, r.Duplex);
|
||||||
|
|
||||||
_logger.LogInformation("Druck OK: {File}", job.FilePath);
|
_logger.LogInformation("Druck OK: {File}", job.FilePath);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
catch (PrintConfigException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Druck abgelehnt: {Message} ({File})", ex.Message, job.FilePath);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -54,15 +60,58 @@ public class PrintService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PrinterProfile ResolveProfile(PrintJob job)
|
/// <summary>
|
||||||
|
/// Auflösung: Override (Profilname oder Druckername) > Profil aus Postfach/Ordner > erstes Profil > Defaults.
|
||||||
|
/// Ein Override der einem Profilnamen entspricht wird zum Profil aufgelöst (Drucker, Fach, Duplex, Kopien).
|
||||||
|
/// </summary>
|
||||||
|
private PrintResult Resolve(PrintJob job)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(job.PrinterProfileName))
|
var printerOverride = job.PrinterOverride?.Trim();
|
||||||
|
PrinterProfile? profile = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(printerOverride))
|
||||||
{
|
{
|
||||||
var p = _options.PrinterProfiles.FirstOrDefault(x =>
|
profile = FindProfile(printerOverride);
|
||||||
x.Name.Equals(job.PrinterProfileName, StringComparison.OrdinalIgnoreCase));
|
if (profile != null) printerOverride = null;
|
||||||
if (p != null) return p;
|
|
||||||
}
|
}
|
||||||
return _options.PrinterProfiles.FirstOrDefault() ?? new PrinterProfile();
|
|
||||||
|
if (profile == null && !string.IsNullOrWhiteSpace(job.PrinterProfileName))
|
||||||
|
profile = FindProfile(job.PrinterProfileName)
|
||||||
|
?? throw new PrintConfigException(
|
||||||
|
$"Unbekanntes Drucker-Profil '{job.PrinterProfileName}'. Vorhanden: {ProfileNames()}");
|
||||||
|
|
||||||
|
profile ??= _options.PrinterProfiles.FirstOrDefault() ?? new PrinterProfile();
|
||||||
|
|
||||||
|
var requested = !string.IsNullOrEmpty(printerOverride) ? printerOverride : profile.PrinterName;
|
||||||
|
var printer = string.IsNullOrWhiteSpace(requested) ? GetDefaultPrinter() : MatchInstalledPrinter(requested);
|
||||||
|
|
||||||
|
var paperSource = !string.IsNullOrEmpty(job.PaperSourceOverride) ? job.PaperSourceOverride : profile.PaperSource;
|
||||||
|
var copies = Math.Max(1, job.CopiesOverride ?? profile.Copies);
|
||||||
|
|
||||||
|
return new PrintResult(profile.Name, printer, paperSource ?? "", profile.Duplex ?? "none", copies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrinterProfile? FindProfile(string name) =>
|
||||||
|
_options.PrinterProfiles.FirstOrDefault(x =>
|
||||||
|
x.Name.Equals(name.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private string ProfileNames() =>
|
||||||
|
_options.PrinterProfiles.Count == 0 ? "-" : string.Join(", ", _options.PrinterProfiles.Select(p => p.Name));
|
||||||
|
|
||||||
|
private string MatchInstalledPrinter(string name)
|
||||||
|
{
|
||||||
|
var installed = PrinterSettings.InstalledPrinters.Cast<string>().ToList();
|
||||||
|
return installed.FirstOrDefault(p => p.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? throw new PrintConfigException(
|
||||||
|
$"Drucker oder Profil '{name}' nicht gefunden. Profile: {ProfileNames()} | Drucker: {string.Join(", ", installed)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetDefaultPrinter()
|
||||||
|
{
|
||||||
|
var name = new PrinterSettings().PrinterName;
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
throw new PrintConfigException("Kein Drucker angegeben und kein Standarddrucker vorhanden.");
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintOnce(string pdfPath, string printerName, string paperSource, string duplex = "none")
|
private void PrintOnce(string pdfPath, string printerName, string paperSource, string duplex = "none")
|
||||||
|
|
@ -78,8 +127,9 @@ public class PrintService
|
||||||
// Fallback Shell-Print
|
// Fallback Shell-Print
|
||||||
_logger.LogWarning("Kein SumatraPDF – Shell-Print (kein Papierfach-Support)");
|
_logger.LogWarning("Kein SumatraPDF – Shell-Print (kein Papierfach-Support)");
|
||||||
var psi = new ProcessStartInfo { FileName = pdfPath, Verb = "print", UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden };
|
var psi = new ProcessStartInfo { FileName = pdfPath, Verb = "print", UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden };
|
||||||
using var p = Process.Start(psi)!;
|
using var p = Process.Start(psi) ?? throw new InvalidOperationException("Shell-Print nicht startbar/bestätigbar.");
|
||||||
p.WaitForExit(30_000);
|
if (!p.WaitForExit(30_000)) throw new TimeoutException("Timeout: Shell-Print");
|
||||||
|
if (p.ExitCode != 0) throw new InvalidOperationException($"Shell-Print meldet Fehler (ExitCode {p.ExitCode}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildPrintSettings(string paperSource, string duplex)
|
private static string BuildPrintSettings(string paperSource, string duplex)
|
||||||
|
|
@ -103,7 +153,8 @@ public class PrintService
|
||||||
var psi = new ProcessStartInfo(exe, args) { UseShellExecute = false, CreateNoWindow = true };
|
var psi = new ProcessStartInfo(exe, args) { UseShellExecute = false, CreateNoWindow = true };
|
||||||
using var p = Process.Start(psi) ?? throw new InvalidOperationException($"Nicht startbar: {exe}");
|
using var p = Process.Start(psi) ?? throw new InvalidOperationException($"Nicht startbar: {exe}");
|
||||||
if (!p.WaitForExit(300_000)) { p.Kill(); throw new TimeoutException($"Timeout: {exe}"); }
|
if (!p.WaitForExit(300_000)) { p.Kill(); throw new TimeoutException($"Timeout: {exe}"); }
|
||||||
if (p.ExitCode != 0) _logger.LogWarning("ExitCode {Code}: {Exe}", p.ExitCode, exe);
|
if (p.ExitCode != 0)
|
||||||
|
throw new InvalidOperationException($"SumatraPDF meldet Fehler (ExitCode {p.ExitCode}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PrinterInfo> GetPrinterInfos()
|
public List<PrinterInfo> GetPrinterInfos()
|
||||||
|
|
@ -121,7 +172,7 @@ public class PrintService
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetAvailablePrinters() => GetPrinterInfos().Select(p => p.Name);
|
public IEnumerable<string> GetAvailablePrinters() => GetPrinterInfos().Select(p => p.Name);
|
||||||
private static string GetDefaultPrinter() => new PrinterSettings().PrinterName;
|
|
||||||
private void TryDelete(string path)
|
private void TryDelete(string path)
|
||||||
{
|
{
|
||||||
try { if (File.Exists(path)) File.Delete(path); }
|
try { if (File.Exists(path)) File.Delete(path); }
|
||||||
|
|
@ -130,3 +181,9 @@ public class PrintService
|
||||||
}
|
}
|
||||||
|
|
||||||
public record PrinterInfo(string Name, List<string> PaperSources);
|
public record PrinterInfo(string Name, List<string> PaperSources);
|
||||||
|
|
||||||
|
/// <summary>Tatsächlich verwendete Druckparameter nach Auflösung von Profil/Override.</summary>
|
||||||
|
public record PrintResult(string Profile, string Printer, string PaperSource, string Duplex, int Copies);
|
||||||
|
|
||||||
|
/// <summary>Ungültige Anfrage/Konfiguration (unbekannter Drucker oder Profil) – kein Systemfehler.</summary>
|
||||||
|
public class PrintConfigException(string message) : Exception(message);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue