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>
|
||||
/// PDF hochladen und drucken.
|
||||
/// 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>
|
||||
[HttpPost("upload")]
|
||||
[Consumes("multipart/form-data")]
|
||||
|
|
@ -34,11 +34,11 @@ public class PrintController : ControllerBase
|
|||
[FromForm] int copies = 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();
|
||||
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);
|
||||
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 file.CopyToAsync(fs);
|
||||
|
||||
_printService.PrintPdf(new PrintJob
|
||||
var r = _printService.PrintPdf(new PrintJob
|
||||
{
|
||||
FilePath = tempFile,
|
||||
MailSubject = file.FileName,
|
||||
|
|
@ -58,12 +58,16 @@ public class PrintController : ControllerBase
|
|||
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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
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);
|
||||
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) };
|
||||
await System.IO.File.WriteAllBytesAsync(tempFile, await http.GetByteArrayAsync(uri));
|
||||
|
||||
_printService.PrintPdf(new PrintJob
|
||||
var r = _printService.PrintPdf(new PrintJob
|
||||
{
|
||||
FilePath = tempFile,
|
||||
MailSubject = uri.Segments.LastOrDefault() ?? "download.pdf",
|
||||
|
|
@ -95,12 +99,16 @@ public class PrintController : ControllerBase
|
|||
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)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
for (int i = 0; i < copies; i++)
|
||||
PrintOnce(job.FilePath, printerName, paperSource, profile.Duplex);
|
||||
var r = Resolve(job);
|
||||
|
||||
_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);
|
||||
return r;
|
||||
}
|
||||
catch (PrintConfigException ex)
|
||||
{
|
||||
_logger.LogWarning("Druck abgelehnt: {Message} ({File})", ex.Message, job.FilePath);
|
||||
throw;
|
||||
}
|
||||
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 =>
|
||||
x.Name.Equals(job.PrinterProfileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (p != null) return p;
|
||||
profile = FindProfile(printerOverride);
|
||||
if (profile != null) printerOverride = null;
|
||||
}
|
||||
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")
|
||||
|
|
@ -78,8 +127,9 @@ public class PrintService
|
|||
// Fallback Shell-Print
|
||||
_logger.LogWarning("Kein SumatraPDF – Shell-Print (kein Papierfach-Support)");
|
||||
var psi = new ProcessStartInfo { FileName = pdfPath, Verb = "print", UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden };
|
||||
using var p = Process.Start(psi)!;
|
||||
p.WaitForExit(30_000);
|
||||
using var p = Process.Start(psi) ?? throw new InvalidOperationException("Shell-Print nicht startbar/bestätigbar.");
|
||||
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)
|
||||
|
|
@ -103,7 +153,8 @@ public class PrintService
|
|||
var psi = new ProcessStartInfo(exe, args) { UseShellExecute = false, CreateNoWindow = true };
|
||||
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.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()
|
||||
|
|
@ -121,7 +172,7 @@ public class PrintService
|
|||
}
|
||||
|
||||
public IEnumerable<string> GetAvailablePrinters() => GetPrinterInfos().Select(p => p.Name);
|
||||
private static string GetDefaultPrinter() => new PrinterSettings().PrinterName;
|
||||
|
||||
private void TryDelete(string path)
|
||||
{
|
||||
try { if (File.Exists(path)) File.Delete(path); }
|
||||
|
|
@ -130,3 +181,9 @@ public class PrintService
|
|||
}
|
||||
|
||||
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