Files
Jacek Pyziak 5fef42ba12 feat(20-windows-client): aplikacja C# WinForms do zdalnego druku etykiet
- System tray app z NotifyIcon + ContextMenuStrip
- Polling API orderPRO (GET /api/print/jobs/pending)
- Pobieranie etykiet PDF i druk przez PdfiumViewer
- Formularz ustawień: URL API, klucz, drukarka, interwał
- Okno logów z historią (ciemny motyw, Consolas)
- Self-contained .NET 8 publish (win-x64)
- Milestone v0.7 Zdalne drukowanie etykiet — COMPLETE

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:49:28 +01:00

160 lines
4.7 KiB
C#

using OrderPROPrint.Forms;
namespace OrderPROPrint.Services;
public class PollingService
{
private readonly PrintApiClient _apiClient;
private readonly PrintService _printService;
private readonly string _printerName;
private readonly int _intervalSeconds;
private readonly Action<string> _onStatusUpdate;
private readonly Action<string> _onError;
private System.Threading.Timer? _timer;
private bool _isProcessing;
private int _totalPrinted;
private readonly object _lock = new();
public bool IsRunning { get; private set; }
public PollingService(
PrintApiClient apiClient,
PrintService printService,
string printerName,
int intervalSeconds,
Action<string> onStatusUpdate,
Action<string> onError)
{
_apiClient = apiClient;
_printService = printService;
_printerName = printerName;
_intervalSeconds = Math.Max(intervalSeconds, 5);
_onStatusUpdate = onStatusUpdate;
_onError = onError;
}
public void Start()
{
if (IsRunning) return;
IsRunning = true;
_timer = new System.Threading.Timer(
async _ => await PollAsync(),
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(_intervalSeconds)
);
_onStatusUpdate($"Aktywne (co {_intervalSeconds}s)");
}
public void Stop()
{
IsRunning = false;
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
_timer?.Dispose();
_timer = null;
}
private async Task PollAsync()
{
lock (_lock)
{
if (_isProcessing) return;
_isProcessing = true;
}
try
{
var jobs = await _apiClient.GetPendingJobsAsync();
LogForm.Log($"Polling: znaleziono {jobs.Count} zleceń");
if (jobs.Count == 0)
{
_onStatusUpdate($"Brak zleceń (wydrukowano: {_totalPrinted})");
return;
}
int printed = 0;
int failed = 0;
string lastError = "";
foreach (var job in jobs)
{
try
{
LogForm.Log($"Job {job.Id}: pobieranie etykiety...");
var labelBytes = await _apiClient.DownloadLabelAsync(job.Id);
LogForm.Log($"Job {job.Id}: pobrano {labelBytes.Length} bajtów");
if (labelBytes.Length == 0)
{
failed++;
lastError = $"Job {job.Id}: pusty plik etykiety";
LogForm.Log($"BŁĄD: {lastError}");
continue;
}
LogForm.Log($"Job {job.Id}: drukowanie na '{_printerName}'...");
bool success = _printService.PrintPdf(labelBytes, _printerName);
if (success)
{
await _apiClient.MarkCompleteAsync(job.Id);
printed++;
_totalPrinted++;
LogForm.Log($"Job {job.Id}: wydrukowano i oznaczono jako complete ✓");
}
else
{
failed++;
lastError = $"Job {job.Id}: druk nie powiódł się";
LogForm.Log($"BŁĄD: {lastError}");
}
}
catch (HttpRequestException ex)
{
failed++;
lastError = $"Job {job.Id}: HTTP {ex.StatusCode} — {ex.Message}";
LogForm.Log($"BŁĄD: {lastError}");
}
catch (Exception ex)
{
failed++;
lastError = $"Job {job.Id}: {ex.GetType().Name} — {ex.Message}";
LogForm.Log($"BŁĄD: {lastError}");
}
}
if (failed > 0)
{
_onStatusUpdate($"Wydrukowano: {printed}, błędy: {failed} (łącznie: {_totalPrinted})");
}
else
{
_onStatusUpdate($"Wydrukowano {printed} etykiet (łącznie: {_totalPrinted})");
}
}
catch (HttpRequestException ex)
{
_onError($"API niedostępne: {ex.Message}");
}
catch (TaskCanceledException)
{
_onError("Timeout połączenia z API");
}
catch (Exception ex)
{
_onError(ex.Message);
}
finally
{
lock (_lock)
{
_isProcessing = false;
}
}
}
}