From cfe141efc988560e8f5b66b12472c9d3a8e6fbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20P=C3=B6ttker?= Date: Sun, 31 May 2026 22:20:22 +0200 Subject: [PATCH] Revamp printer activation settings --- BACKEND_API.md | 18 +- README.md | 11 +- src/LabelPrintAgent/App/SettingsForm.cs | 358 +++++++++++++----- .../App/TrayApplicationContext.cs | 2 +- src/LabelPrintAgent/Backend/BackendClient.cs | 66 ++-- .../Backend/BackendPollingWorker.cs | 2 +- .../Configuration/AppSettings.cs | 2 + .../Configuration/SettingsStore.cs | 3 +- 8 files changed, 329 insertions(+), 133 deletions(-) diff --git a/BACKEND_API.md b/BACKEND_API.md index 47335e9..56bb5ff 100644 --- a/BACKEND_API.md +++ b/BACKEND_API.md @@ -12,7 +12,7 @@ Alle Endpunkte erfordern einen Bearer Token (JWT oder API-Key): Authorization: Bearer {token} ``` -`/printers/register`, `/jobs/next`, `/jobs/:id/image`, `/jobs/:id/printed` und `/jobs/:id/error` benötigen keine spezifische Permission, nur einen gültigen Token. `POST /jobs` und `POST /preview` erfordern `VIEW_SCANNER`. +`/printers/register`, `/printers/:printerId/deactivate`, `/jobs/next`, `/jobs/:id/image`, `/jobs/:id/printed` und `/jobs/:id/error` benötigen keine spezifische Permission, nur einen gültigen Token. `POST /jobs` und `POST /preview` erfordern `VIEW_SCANNER`. ## 1. Job manuell anlegen (Frontend -> Backend) @@ -67,9 +67,9 @@ Content-Type: image/png Body: binäres PNG-Bild. -## 3. Drucker registrieren (Benutzeraktion in den Agent-Einstellungen) +## 3. Drucker aktivieren/deaktivieren (Benutzeraktion in den Agent-Einstellungen) -Der Agent ruft diesen Endpunkt nicht automatisch beim Start auf. Der Benutzer registriert die freigegebenen lokalen Windows-Drucker bewusst über die Agent-Einstellungen. +Der Agent ruft diesen Endpunkt nicht automatisch beim Start auf. Der Benutzer aktiviert lokale Windows-Drucker bewusst über die Agent-Einstellungen. ```http POST /api/label-print-agent/printers/register @@ -84,7 +84,8 @@ Content-Type: application/json "windowsPrinterName": "Zebra GK420d", "dpi": 203, "defaultWidthMm": 101, - "defaultHeightMm": 76 + "defaultHeightMm": 76, + "active": true } ``` @@ -98,6 +99,15 @@ Content-Type: application/json { "ok": true } ``` +Zum Deaktivieren entfernt der Benutzer den Haken `Aktiv`; der Agent ruft standardmäßig folgenden Endpunkt auf: + +```http +POST /api/label-print-agent/printers/{printerId}/deactivate +Content-Type: application/json +``` + +Der Request Body entspricht dem Aktivieren, aber mit `"active": false`. + ## 4. Nächsten Druckjob abrufen (Agent-Polling) ```http diff --git a/README.md b/README.md index 5c2f2de..19ff491 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ Beispiel: "agentId": "PC-BUERO", "encryptedApiToken": "", "registerPrinterPath": "/api/label-print-agent/printers/register", + "deactivatePrinterPath": "/api/label-print-agent/printers/{printerId}/deactivate", "nextJobPath": "/api/label-print-agent/jobs/next", "imagePath": "/api/label-print-agent/jobs/{jobId}/image", "reportSuccessPath": "/api/label-print-agent/jobs/{jobId}/printed", @@ -128,7 +129,8 @@ Beispiel: "windowsPrinterName": "DYMO LabelWriter 450", "dpi": 300, "defaultWidthMm": 57, - "defaultHeightMm": 32 + "defaultHeightMm": 32, + "isActive": true }, { "printerId": "PC-BUERO_ZEBRA_GK420D", @@ -136,7 +138,8 @@ Beispiel: "windowsPrinterName": "Zebra GK420d", "dpi": 203, "defaultWidthMm": 101, - "defaultHeightMm": 76 + "defaultHeightMm": 76, + "isActive": false } ], "worker": { @@ -148,7 +151,7 @@ Beispiel: Der API-Token wird lokal mit Windows DPAPI verschlüsselt gespeichert. -Der Agent registriert Drucker nicht automatisch. Im Tab `Drucker` kann der Benutzer die freigegebenen Einträge aus `printers` bewusst mit `Im Backend registrieren` an das Backend melden. Das Feld `printer` bleibt als Kompatibilitätsfeld für ältere Einstellungen erhalten; neue Jobs werden anhand von `windowsPrinterName` aus der Backend-Antwort auf den passenden lokalen Windows-Drucker gedruckt. +Der Agent registriert Drucker nicht automatisch. Im Tab `Drucker` steht links die Druckerliste; rechts werden Aktiv-Status, Breite, Höhe und DPI pro Drucker gepflegt. Wird `Aktiv` gesetzt, registriert der Agent den Drucker im Backend. Wird der Haken entfernt, ruft der Agent den Deaktivierungs-Endpunkt auf. Das Feld `printer` bleibt als Kompatibilitätsfeld für ältere Einstellungen erhalten; neue Jobs werden anhand von `windowsPrinterName` aus der Backend-Antwort auf den passenden lokalen Windows-Drucker gedruckt. ## Dymo-Druck @@ -166,7 +169,7 @@ Wichtig: 1. Anwendung starten. 2. Tray-Symbol öffnen. 3. Im Tab `Backend` BaseUrl, AgentId und optional API-Token eintragen. -4. Im Tab `Drucker` die freigegebenen Drucker konfigurieren und mit `Im Backend registrieren` ans Backend melden. +4. Im Tab `Drucker` links einen Drucker auswählen und rechts `Aktiv`, Breite, Höhe und DPI setzen. 5. Im Tab `Allgemein` Polling aktivieren und Intervall setzen. 6. Mit `Jetzt prüfen` kann sofort ein Poll-Lauf ausgelöst werden; dabei werden alle aktuell verfügbaren Jobs verarbeitet. diff --git a/src/LabelPrintAgent/App/SettingsForm.cs b/src/LabelPrintAgent/App/SettingsForm.cs index ea489b9..4e646a5 100644 --- a/src/LabelPrintAgent/App/SettingsForm.cs +++ b/src/LabelPrintAgent/App/SettingsForm.cs @@ -17,7 +17,14 @@ internal sealed class SettingsForm : Form private readonly BackendClient _backendClient; private readonly BackendPollingWorker _backendWorker; private readonly UpdateService _updateService; + private readonly Dictionary _printerConfigs = new(StringComparer.OrdinalIgnoreCase); private UpdateCheckResult? _lastUpdateCheck; + private bool _loadingPrinters; + private bool _loadingPrinterConfig; + private string? _selectedSharedPrinterName; + private decimal _defaultLabelWidthMm = 57; + private decimal _defaultLabelHeightMm = 32; + private int _defaultPrinterDpi = 203; private readonly CheckBox _autoStartCheckBox = new() { Text = "Autostart aktivieren", AutoSize = true }; private readonly CheckBox _workerEnabled = new() { Text = "Backend automatisch abfragen", AutoSize = true }; @@ -27,6 +34,8 @@ internal sealed class SettingsForm : Form private readonly TextBox _backendBaseUrl = new() { Width = 420 }; private readonly TextBox _agentId = new() { Width = 260 }; private readonly TextBox _apiToken = new() { Width = 420, UseSystemPasswordChar = true }; + private readonly TextBox _registerPrinterPath = new() { Width = 420 }; + private readonly TextBox _deactivatePrinterPath = new() { Width = 420 }; private readonly TextBox _nextJobPath = new() { Width = 420 }; private readonly TextBox _imagePath = new() { Width = 420 }; private readonly TextBox _reportSuccessPath = new() { Width = 420 }; @@ -41,10 +50,12 @@ internal sealed class SettingsForm : Form private readonly TextBox _updateAsset = new() { Width = 540, ReadOnly = true }; private readonly Button _installUpdateButton; - private readonly ComboBox _printer = new() { Width = 360, DropDownStyle = ComboBoxStyle.DropDownList }; - private readonly CheckedListBox _sharedPrinters = new() { Width = 360, Height = 125, CheckOnClick = true }; + private readonly ListBox _printerList = new() { Width = 320, Height = 285 }; + private readonly CheckBox _printerActive = new() { Text = "Aktiv", AutoSize = true }; + private readonly TextBox _windowsPrinterName = new() { Width = 360, ReadOnly = true }; private readonly NumericUpDown _labelWidth = new() { Minimum = 1, Maximum = 500, DecimalPlaces = 1, Value = 57, Width = 100 }; private readonly NumericUpDown _labelHeight = new() { Minimum = 1, Maximum = 500, DecimalPlaces = 1, Value = 32, Width = 100 }; + private readonly NumericUpDown _printerDpi = new() { Minimum = 50, Maximum = 2400, Value = 203, Width = 100 }; private readonly TextBox _status = new() { @@ -81,6 +92,11 @@ internal sealed class SettingsForm : Form tabs.TabPages.Add(CreateUpdatesTab()); tabs.TabPages.Add(CreateStatusTab()); Controls.Add(tabs); + _printerList.SelectedIndexChanged += (_, _) => SelectPrinter(GetPrinterName(_printerList.SelectedItem)); + _printerActive.CheckedChanged += async (_, _) => await SetSelectedPrinterActiveFromUiAsync(); + _labelWidth.ValueChanged += (_, _) => SaveSelectedPrinterConfig(); + _labelHeight.ValueChanged += (_, _) => SaveSelectedPrinterConfig(); + _printerDpi.ValueChanged += (_, _) => SaveSelectedPrinterConfig(); Load += (_, _) => { @@ -106,33 +122,41 @@ internal sealed class SettingsForm : Form panel.Controls.Add(Row(20, Label("BaseUrl", 160), _backendBaseUrl)); panel.Controls.Add(Row(60, Label("AgentId", 160), _agentId)); panel.Controls.Add(Row(100, Label("API Token", 160), _apiToken)); - panel.Controls.Add(Row(140, Label("NextJobPath", 160), _nextJobPath)); - panel.Controls.Add(Row(180, Label("ImagePath", 160), _imagePath)); - panel.Controls.Add(Row(220, Label("ReportSuccessPath", 160), _reportSuccessPath)); - panel.Controls.Add(Row(260, Label("ReportErrorPath", 160), _reportErrorPath)); - panel.Controls.Add(Row(300, Label("SSE", 160), _useServerSentEvents)); - panel.Controls.Add(Row(340, Label("EventsPath", 160), _eventsPath)); - panel.Controls.Add(ButtonAt("Speichern", 180, 395, (_, _) => SaveBackend(showMessage: true))); - panel.Controls.Add(ButtonAt("Jetzt prüfen", 300, 395, async (_, _) => await PollOnceFromUiAsync())); + panel.Controls.Add(Row(140, Label("RegisterPrinterPath", 160), _registerPrinterPath)); + panel.Controls.Add(Row(180, Label("DeactivatePrinterPath", 160), _deactivatePrinterPath)); + panel.Controls.Add(Row(220, Label("NextJobPath", 160), _nextJobPath)); + panel.Controls.Add(Row(260, Label("ImagePath", 160), _imagePath)); + panel.Controls.Add(Row(300, Label("ReportSuccessPath", 160), _reportSuccessPath)); + panel.Controls.Add(Row(340, Label("ReportErrorPath", 160), _reportErrorPath)); + panel.Controls.Add(Row(380, Label("SSE", 160), _useServerSentEvents)); + panel.Controls.Add(Row(420, Label("EventsPath", 160), _eventsPath)); + panel.Controls.Add(ButtonAt("Speichern", 180, 475, (_, _) => SaveBackend(showMessage: true))); + panel.Controls.Add(ButtonAt("Jetzt prüfen", 300, 475, async (_, _) => await PollOnceFromUiAsync())); return new TabPage("Backend") { Controls = { panel } }; } private TabPage CreatePrinterTab() { var panel = CreatePaddedPanel(); - panel.Controls.Add(Row(20, Label("Standarddrucker", 120), _printer)); - var sharedLabel = Label("Freigegeben", 120); - sharedLabel.Left = 20; - sharedLabel.Top = 60; - _sharedPrinters.Left = 145; - _sharedPrinters.Top = 60; - panel.Controls.Add(sharedLabel); - panel.Controls.Add(_sharedPrinters); - panel.Controls.Add(Row(200, Label("Breite", 120), _labelWidth, Label("mm", 40))); - panel.Controls.Add(Row(240, Label("Höhe", 120), _labelHeight, Label("mm", 40))); - panel.Controls.Add(ButtonAt("Drucker neu laden", 140, 295, (_, _) => LoadPrinters())); - panel.Controls.Add(ButtonAt("Speichern", 300, 295, (_, _) => SavePrinter(showMessage: true))); - panel.Controls.Add(ButtonAt("Im Backend registrieren", 430, 295, async (_, _) => await RegisterPrintersFromUiAsync())); + var listLabel = Label("Drucker", 120); + listLabel.Left = 20; + listLabel.Top = 20; + _printerList.Left = 20; + _printerList.Top = 55; + panel.Controls.Add(listLabel); + panel.Controls.Add(_printerList); + + var detailLeft = 380; + _printerActive.Left = detailLeft; + _printerActive.Top = 55; + panel.Controls.Add(_printerActive); + panel.Controls.Add(RowAt(detailLeft, 95, Label("Windows-Name", 120), _windowsPrinterName)); + panel.Controls.Add(RowAt(detailLeft, 135, Label("Breite", 120), _labelWidth, Label("mm", 40))); + panel.Controls.Add(RowAt(detailLeft, 175, Label("Höhe", 120), _labelHeight, Label("mm", 40))); + panel.Controls.Add(RowAt(detailLeft, 215, Label("DPI", 120), _printerDpi)); + + panel.Controls.Add(ButtonAt("Drucker neu laden", 20, 365, (_, _) => LoadPrinters())); + panel.Controls.Add(ButtonAt("Speichern", detailLeft, 365, (_, _) => SavePrinter(showMessage: true))); return new TabPage("Drucker") { Controls = { panel } }; } @@ -168,6 +192,8 @@ internal sealed class SettingsForm : Form _backendBaseUrl.Text = settings.Backend.BaseUrl; _agentId.Text = settings.Backend.AgentId; _apiToken.Text = _settingsStore.DecryptBackendApiToken(settings); + _registerPrinterPath.Text = settings.Backend.RegisterPrinterPath; + _deactivatePrinterPath.Text = settings.Backend.DeactivatePrinterPath; _nextJobPath.Text = settings.Backend.NextJobPath; _imagePath.Text = settings.Backend.ImagePath; _reportSuccessPath.Text = settings.Backend.ReportSuccessPath; @@ -183,34 +209,54 @@ internal sealed class SettingsForm : Form _installUpdateButton.Enabled = false; _lastUpdateCheck = null; - var primaryPrinter = GetPrimaryPrinter(settings); - _labelWidth.Value = primaryPrinter?.DefaultWidthMm ?? settings.Printer.LabelWidthMm; - _labelHeight.Value = primaryPrinter?.DefaultHeightMm ?? settings.Printer.LabelHeightMm; + _defaultLabelWidthMm = settings.Printer.LabelWidthMm; + _defaultLabelHeightMm = settings.Printer.LabelHeightMm; + _defaultPrinterDpi = settings.Printer.Dpi > 0 ? settings.Printer.Dpi : 203; + SetPrinterEditorEnabled(false); } private void LoadPrinters() { var settings = _settingsStore.Load(); var selected = GetPrimaryPrinter(settings)?.WindowsPrinterName ?? settings.Printer.PrinterName; - _printer.Items.Clear(); - _sharedPrinters.Items.Clear(); - var sharedPrinterNames = settings.Printers - .Select(printer => printer.WindowsPrinterName) - .Where(name => !string.IsNullOrWhiteSpace(name)) - .ToHashSet(StringComparer.OrdinalIgnoreCase); + _loadingPrinters = true; + _selectedSharedPrinterName = null; + _printerConfigs.Clear(); + foreach (var printer in settings.Printers.Where(printer => !string.IsNullOrWhiteSpace(printer.WindowsPrinterName))) + { + _printerConfigs[printer.WindowsPrinterName] = ClonePrinterConfig(printer); + } + + _printerList.Items.Clear(); var printers = _printerService.GetInstalledPrinters(); foreach (var printer in printers) { - _printer.Items.Add(printer); - _sharedPrinters.Items.Add(printer, sharedPrinterNames.Contains(printer.Name)); + EnsurePrinterConfig(printer.Name); + _printerList.Items.Add(printer); } - var configuredPrinter = printers.FirstOrDefault(printer => string.Equals(printer.Name, selected, StringComparison.OrdinalIgnoreCase)); - _printer.SelectedItem = configuredPrinter ?? printers.FirstOrDefault(printer => printer.IsDefault); - if (_printer.SelectedItem is null && _printer.Items.Count > 0) + foreach (var printerName in _printerConfigs.Keys + .Where(name => printers.All(printer => !string.Equals(printer.Name, name, StringComparison.OrdinalIgnoreCase))) + .OrderBy(name => name)) { - _printer.SelectedIndex = 0; + _printerList.Items.Add(printerName); } + + _loadingPrinters = false; + var selectedIndex = FindPrinterIndex(selected); + if (selectedIndex < 0 && _printerList.Items.Count > 0) + { + selectedIndex = 0; + } + + if (selectedIndex >= 0) + { + _printerList.SelectedIndex = selectedIndex; + SelectPrinter(GetPrinterName(_printerList.SelectedItem)); + return; + } + + SetPrinterEditorEnabled(false); } private void SaveGeneral() @@ -231,6 +277,8 @@ internal sealed class SettingsForm : Form var normalizedToken = SettingsStore.NormalizeBackendApiToken(_apiToken.Text); settings.Backend.EncryptedApiToken = _settingsStore.EncryptPassword(normalizedToken); _apiToken.Text = normalizedToken; + settings.Backend.RegisterPrinterPath = _registerPrinterPath.Text.Trim(); + settings.Backend.DeactivatePrinterPath = _deactivatePrinterPath.Text.Trim(); settings.Backend.NextJobPath = _nextJobPath.Text.Trim(); settings.Backend.ImagePath = _imagePath.Text.Trim(); settings.Backend.ReportSuccessPath = _reportSuccessPath.Text.Trim(); @@ -249,17 +297,19 @@ internal sealed class SettingsForm : Form { var settings = _settingsStore.Load(); var selectedPrinterName = GetSelectedPrinterName() ?? string.Empty; - var sharedPrinterNames = GetSharedPrinterNames(); + SaveSelectedPrinterConfig(); settings.Printer.PrinterName = selectedPrinterName; settings.Printer.LabelWidthMm = _labelWidth.Value; settings.Printer.LabelHeightMm = _labelHeight.Value; - settings.Printers = sharedPrinterNames - .Select(printerName => BuildPrinterConfig(settings, printerName)) + settings.Printer.Dpi = (int)_printerDpi.Value; + settings.Printers = _printerConfigs.Keys + .OrderBy(printerName => printerName) + .Select(printerName => BuildPrinterConfig(settings, printerName, fallbackToEditor: false)) .ToList(); if (settings.Printers.Count == 0 && !string.IsNullOrWhiteSpace(selectedPrinterName)) { - settings.Printers.Add(BuildPrinterConfig(settings, selectedPrinterName)); + settings.Printers.Add(BuildPrinterConfig(settings, selectedPrinterName, fallbackToEditor: true)); } _settingsStore.Save(settings); @@ -301,34 +351,6 @@ internal sealed class SettingsForm : Form } } - private async Task RegisterPrintersFromUiAsync() - { - try - { - SaveBackend(showMessage: false); - SavePrinter(showMessage: false); - - var settings = _settingsStore.Load(); - var printerCount = settings.Printers.Count(printer => !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)); - if (printerCount == 0) - { - MessageBox.Show("Bitte zuerst mindestens einen Drucker konfigurieren.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - AppendStatus($"{printerCount} Drucker werden im Backend registriert..."); - await _backendClient.RegisterPrintersAsync(); - AppendStatus($"{printerCount} Drucker im Backend registriert."); - MessageBox.Show("Drucker wurden im Backend registriert.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (Exception ex) - { - Log.Error(ex, "Printer registration failed"); - AppendStatus($"Druckerregistrierung fehlgeschlagen: {ex.Message}"); - MessageBox.Show($"Druckerregistrierung fehlgeschlagen: {ex.Message}", Text, MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - private async Task CheckForUpdateFromUiAsync() { try @@ -408,34 +430,114 @@ internal sealed class SettingsForm : Form private string? GetSelectedPrinterName() { - return _printer.SelectedItem switch + return GetPrinterName(_printerList.SelectedItem); + } + + private void SelectPrinter(string? printerName) + { + if (_loadingPrinters) { - PrinterInfo printerInfo => printerInfo.Name, - string printerName => printerName, - _ => null + return; + } + + if (string.Equals(_selectedSharedPrinterName, printerName, StringComparison.OrdinalIgnoreCase)) + { + return; + } + + SaveSelectedPrinterConfig(); + if (string.IsNullOrWhiteSpace(printerName) || !_printerConfigs.TryGetValue(printerName, out var config)) + { + _selectedSharedPrinterName = null; + SetPrinterEditorEnabled(false); + return; + } + + _selectedSharedPrinterName = printerName; + SetPrinterEditorEnabled(true); + _loadingPrinterConfig = true; + try + { + _windowsPrinterName.Text = config.WindowsPrinterName; + _printerActive.Checked = config.IsActive; + _labelWidth.Value = Math.Clamp(config.DefaultWidthMm, (int)_labelWidth.Minimum, (int)_labelWidth.Maximum); + _labelHeight.Value = Math.Clamp(config.DefaultHeightMm, (int)_labelHeight.Minimum, (int)_labelHeight.Maximum); + _printerDpi.Value = Math.Clamp(config.Dpi, (int)_printerDpi.Minimum, (int)_printerDpi.Maximum); + } + finally + { + _loadingPrinterConfig = false; + } + } + + private void SaveSelectedPrinterConfig() + { + if (_loadingPrinters || _loadingPrinterConfig || string.IsNullOrWhiteSpace(_selectedSharedPrinterName)) + { + return; + } + + var config = EnsurePrinterConfig(_selectedSharedPrinterName); + config.DefaultWidthMm = Math.Max(1, (int)Math.Round(_labelWidth.Value)); + config.DefaultHeightMm = Math.Max(1, (int)Math.Round(_labelHeight.Value)); + config.Dpi = (int)_printerDpi.Value; + config.IsActive = _printerActive.Checked; + } + + private async Task SetSelectedPrinterActiveFromUiAsync() + { + if (_loadingPrinters || _loadingPrinterConfig || string.IsNullOrWhiteSpace(_selectedSharedPrinterName)) + { + return; + } + + try + { + SaveBackend(showMessage: false); + SaveSelectedPrinterConfig(); + var config = BuildPrinterConfig(_settingsStore.Load(), _selectedSharedPrinterName, fallbackToEditor: false); + config.IsActive = _printerActive.Checked; + SavePrinter(showMessage: false); + await _backendClient.SetPrinterActiveAsync(config, config.IsActive); + AppendStatus($"Drucker {config.WindowsPrinterName} {(config.IsActive ? "aktiviert" : "deaktiviert")}."); + } + catch (Exception ex) + { + Log.Error(ex, "Could not update printer active state"); + AppendStatus($"Aktiv-Status konnte nicht synchronisiert werden: {ex.Message}"); + MessageBox.Show($"Aktiv-Status konnte nicht synchronisiert werden: {ex.Message}", Text, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private PrinterConfig EnsurePrinterConfig(string printerName) + { + if (_printerConfigs.TryGetValue(printerName, out var config)) + { + return config; + } + + config = new PrinterConfig + { + PrinterId = SettingsStore.BuildPrinterId(_agentId.Text.Trim(), printerName), + Name = printerName, + WindowsPrinterName = printerName, + Dpi = _defaultPrinterDpi, + DefaultWidthMm = Math.Max(1, (int)Math.Round(_defaultLabelWidthMm)), + DefaultHeightMm = Math.Max(1, (int)Math.Round(_defaultLabelHeightMm)), + IsActive = false }; + _printerConfigs[printerName] = config; + return config; } - private List GetSharedPrinterNames() + private PrinterConfig BuildPrinterConfig(AppSettings settings, string printerName, bool fallbackToEditor) { - return _sharedPrinters.CheckedItems - .Cast() - .Select(item => item switch - { - PrinterInfo printerInfo => printerInfo.Name, - string printerName => printerName, - _ => null - }) - .Where(printerName => !string.IsNullOrWhiteSpace(printerName)) - .Cast() - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - - private PrinterConfig BuildPrinterConfig(AppSettings settings, string printerName) - { - var existingPrinter = settings.Printers.FirstOrDefault( - printer => string.Equals(printer.WindowsPrinterName, printerName, StringComparison.OrdinalIgnoreCase)); + var existingPrinter = _printerConfigs.TryGetValue(printerName, out var config) + ? config + : settings.Printers.FirstOrDefault(printer => string.Equals(printer.WindowsPrinterName, printerName, StringComparison.OrdinalIgnoreCase)); + var width = fallbackToEditor ? (int)Math.Round(_labelWidth.Value) : existingPrinter?.DefaultWidthMm ?? (int)Math.Round(settings.Printer.LabelWidthMm); + var height = fallbackToEditor ? (int)Math.Round(_labelHeight.Value) : existingPrinter?.DefaultHeightMm ?? (int)Math.Round(settings.Printer.LabelHeightMm); + var dpi = fallbackToEditor ? (int)_printerDpi.Value : existingPrinter?.Dpi ?? settings.Printer.Dpi; return new PrinterConfig { PrinterId = string.IsNullOrWhiteSpace(existingPrinter?.PrinterId) @@ -443,15 +545,70 @@ internal sealed class SettingsForm : Form : existingPrinter.PrinterId, Name = string.IsNullOrWhiteSpace(existingPrinter?.Name) ? printerName : existingPrinter.Name, WindowsPrinterName = printerName, - Dpi = existingPrinter?.Dpi > 0 ? existingPrinter.Dpi : settings.Printer.Dpi, - DefaultWidthMm = Math.Max(1, (int)Math.Round(_labelWidth.Value)), - DefaultHeightMm = Math.Max(1, (int)Math.Round(_labelHeight.Value)) + Dpi = Math.Max(1, dpi), + DefaultWidthMm = Math.Max(1, width), + DefaultHeightMm = Math.Max(1, height), + IsActive = existingPrinter?.IsActive ?? false }; } private static PrinterConfig? GetPrimaryPrinter(AppSettings settings) { - return settings.Printers.FirstOrDefault(printer => !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)); + return settings.Printers.FirstOrDefault(printer => printer.IsActive && !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)) + ?? settings.Printers.FirstOrDefault(printer => !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)); + } + + private int FindPrinterIndex(string printerName) + { + for (var index = 0; index < _printerList.Items.Count; index++) + { + if (string.Equals(GetPrinterName(_printerList.Items[index]), printerName, StringComparison.OrdinalIgnoreCase)) + { + return index; + } + } + + return -1; + } + + private static string? GetPrinterName(object? item) + { + return item switch + { + PrinterInfo printerInfo => printerInfo.Name, + string printerName => printerName, + _ => null + }; + } + + private static PrinterConfig ClonePrinterConfig(PrinterConfig printer) + { + return new PrinterConfig + { + PrinterId = printer.PrinterId, + Name = printer.Name, + WindowsPrinterName = printer.WindowsPrinterName, + Dpi = printer.Dpi, + DefaultWidthMm = printer.DefaultWidthMm, + DefaultHeightMm = printer.DefaultHeightMm, + IsActive = printer.IsActive + }; + } + + private void SetPrinterEditorEnabled(bool enabled) + { + _labelWidth.Enabled = enabled; + _labelHeight.Enabled = enabled; + _printerDpi.Enabled = enabled; + _printerActive.Enabled = enabled; + _windowsPrinterName.Enabled = enabled; + if (!enabled) + { + _loadingPrinterConfig = true; + _windowsPrinterName.Text = string.Empty; + _printerActive.Checked = false; + _loadingPrinterConfig = false; + } } private void AppendStatus(string message) @@ -483,10 +640,15 @@ internal sealed class SettingsForm : Form private static Label Label(string text, int width) => new() { Text = text, Width = width, TextAlign = ContentAlignment.MiddleLeft }; private static FlowLayoutPanel Row(int top, params Control[] controls) + { + return RowAt(20, top, controls); + } + + private static FlowLayoutPanel RowAt(int left, int top, params Control[] controls) { var row = new FlowLayoutPanel { - Left = 20, + Left = left, Top = top, Width = 760, Height = 32, diff --git a/src/LabelPrintAgent/App/TrayApplicationContext.cs b/src/LabelPrintAgent/App/TrayApplicationContext.cs index 25d20e1..33cc26d 100644 --- a/src/LabelPrintAgent/App/TrayApplicationContext.cs +++ b/src/LabelPrintAgent/App/TrayApplicationContext.cs @@ -116,7 +116,7 @@ internal sealed class TrayApplicationContext : ApplicationContext } var configuredPrinters = settings.Printers - .Where(printer => !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)) + .Where(printer => printer.IsActive && !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)) .ToList(); if (configuredPrinters.Count == 0) { diff --git a/src/LabelPrintAgent/Backend/BackendClient.cs b/src/LabelPrintAgent/Backend/BackendClient.cs index 2627231..0b01d7b 100644 --- a/src/LabelPrintAgent/Backend/BackendClient.cs +++ b/src/LabelPrintAgent/Backend/BackendClient.cs @@ -38,34 +38,18 @@ public sealed class BackendClient public async Task RegisterPrintersAsync(CancellationToken cancellationToken = default) { var settings = _settingsStore.Load(); - foreach (var printer in settings.Printers.Where(IsRegisterablePrinter)) + foreach (var printer in settings.Printers.Where(printer => printer.IsActive).Where(IsRegisterablePrinter)) { - var body = new - { - printerId = printer.PrinterId, - agentId = settings.Backend.AgentId, - name = printer.Name, - windowsPrinterName = printer.WindowsPrinterName, - dpi = printer.Dpi, - defaultWidthMm = printer.DefaultWidthMm, - defaultHeightMm = printer.DefaultHeightMm - }; - - using var request = CreateRequest( - HttpMethod.Post, - MakeAbsoluteUrl(settings.Backend.BaseUrl, settings.Backend.RegisterPrinterPath), - settings); - request.Content = JsonContent.Create(body); - using var response = await _httpClient.SendAsync(request, cancellationToken); - await EnsureSuccessWithDiagnosticsAsync(response, request, cancellationToken); - Log.Information( - "Registered printer {PrinterId} ({WindowsPrinterName}) for agent {AgentId}", - printer.PrinterId, - printer.WindowsPrinterName, - settings.Backend.AgentId); + await SetPrinterActiveAsync(settings, printer, active: true, cancellationToken); } } + public async Task SetPrinterActiveAsync(PrinterConfig printer, bool active, CancellationToken cancellationToken = default) + { + var settings = _settingsStore.Load(); + await SetPrinterActiveAsync(settings, printer, active, cancellationToken); + } + public async Task GetLabelImageAsync(BackendLabelJob job, CancellationToken cancellationToken = default) { if (!string.IsNullOrWhiteSpace(job.LabelImageBase64)) @@ -120,6 +104,40 @@ public sealed class BackendClient await EnsureSuccessWithDiagnosticsAsync(response, request, cancellationToken); } + private async Task SetPrinterActiveAsync(AppSettings settings, PrinterConfig printer, bool active, CancellationToken cancellationToken) + { + if (!IsRegisterablePrinter(printer)) + { + throw new InvalidOperationException("Drucker kann ohne PrinterId und WindowsPrinterName nicht synchronisiert werden."); + } + + var body = new + { + printerId = printer.PrinterId, + agentId = settings.Backend.AgentId, + name = printer.Name, + windowsPrinterName = printer.WindowsPrinterName, + dpi = printer.Dpi, + defaultWidthMm = printer.DefaultWidthMm, + defaultHeightMm = printer.DefaultHeightMm, + active + }; + + var path = active + ? settings.Backend.RegisterPrinterPath + : settings.Backend.DeactivatePrinterPath.Replace("{printerId}", Uri.EscapeDataString(printer.PrinterId), StringComparison.OrdinalIgnoreCase); + using var request = CreateRequest(HttpMethod.Post, MakeAbsoluteUrl(settings.Backend.BaseUrl, path), settings); + request.Content = JsonContent.Create(body); + using var response = await _httpClient.SendAsync(request, cancellationToken); + await EnsureSuccessWithDiagnosticsAsync(response, request, cancellationToken); + Log.Information( + "{Action} printer {PrinterId} ({WindowsPrinterName}) for agent {AgentId}", + active ? "Activated" : "Deactivated", + printer.PrinterId, + printer.WindowsPrinterName, + settings.Backend.AgentId); + } + public async Task WatchServerSentEventsAsync(Func onLabelJobAvailable, CancellationToken cancellationToken = default) { var settings = _settingsStore.Load(); diff --git a/src/LabelPrintAgent/Backend/BackendPollingWorker.cs b/src/LabelPrintAgent/Backend/BackendPollingWorker.cs index be5cc29..12b7f8d 100644 --- a/src/LabelPrintAgent/Backend/BackendPollingWorker.cs +++ b/src/LabelPrintAgent/Backend/BackendPollingWorker.cs @@ -55,7 +55,7 @@ public sealed class BackendPollingWorker : IDisposable } var configuredPrinters = settings.Printers - .Where(printer => !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)) + .Where(printer => printer.IsActive && !string.IsNullOrWhiteSpace(printer.WindowsPrinterName)) .ToList(); if (configuredPrinters.Count == 0) { diff --git a/src/LabelPrintAgent/Configuration/AppSettings.cs b/src/LabelPrintAgent/Configuration/AppSettings.cs index cfd82da..3b8565d 100644 --- a/src/LabelPrintAgent/Configuration/AppSettings.cs +++ b/src/LabelPrintAgent/Configuration/AppSettings.cs @@ -22,6 +22,7 @@ public sealed class BackendSettings public string AgentId { get; set; } = Environment.MachineName; public string EncryptedApiToken { get; set; } = string.Empty; public string RegisterPrinterPath { get; set; } = "/api/label-print-agent/printers/register"; + public string DeactivatePrinterPath { get; set; } = "/api/label-print-agent/printers/{printerId}/deactivate"; public string NextJobPath { get; set; } = "/api/label-print-agent/jobs/next"; public string ImagePath { get; set; } = "/api/label-print-agent/jobs/{jobId}/image"; public string ReportSuccessPath { get; set; } = "/api/label-print-agent/jobs/{jobId}/printed"; @@ -53,6 +54,7 @@ public sealed class PrinterConfig public int Dpi { get; set; } = 203; public int DefaultWidthMm { get; set; } = 57; public int DefaultHeightMm { get; set; } = 32; + public bool IsActive { get; set; } = true; } public sealed class WorkerSettings diff --git a/src/LabelPrintAgent/Configuration/SettingsStore.cs b/src/LabelPrintAgent/Configuration/SettingsStore.cs index bc92b87..b6310d3 100644 --- a/src/LabelPrintAgent/Configuration/SettingsStore.cs +++ b/src/LabelPrintAgent/Configuration/SettingsStore.cs @@ -101,7 +101,8 @@ public sealed class SettingsStore WindowsPrinterName = settings.Printer.PrinterName, Dpi = settings.Printer.Dpi, DefaultWidthMm = Math.Max(1, (int)Math.Round(settings.Printer.LabelWidthMm)), - DefaultHeightMm = Math.Max(1, (int)Math.Round(settings.Printer.LabelHeightMm)) + DefaultHeightMm = Math.Max(1, (int)Math.Round(settings.Printer.LabelHeightMm)), + IsActive = true }); }