using System.Drawing.Printing; using System.Management; using CloudPrint.Client.Models; using CloudPrint.Client.Services.Abstractions; using CloudPrint.Client.Services.Printing; namespace CloudPrint.Client.Services; /// /// 真实打印服务:枚举 Windows 打印机,按文件类型提交到图片/PDF/RAW 打印执行器。 /// public sealed class PrintService : IPrintService { private readonly IReadOnlyList _executors; private readonly IPrinterStatusReader _statusReader; private readonly IWindowsPrintQueueService _queueService; public PrintService() : this(CreateDefaultExecutors(), new WindowsPrinterStatusReader(), new WindowsPrintQueueService()) { } private static IReadOnlyList CreateDefaultExecutors() { var queueService = new WindowsPrintQueueService(); return new IPrintJobExecutor[] { new ImagePrintJobExecutor(queueService), new PdfPrintJobExecutor(queueService), new RawPrintJobExecutor() }; } public PrintService(IEnumerable executors, IPrinterStatusReader statusReader, IWindowsPrintQueueService queueService) { _executors = executors.ToList(); _statusReader = statusReader; _queueService = queueService; if (_executors.Count == 0) { throw new ArgumentException("至少需要一个打印执行器", nameof(executors)); } } public IReadOnlyList GetPrinters() { var printers = new List(); var index = 1; foreach (string printerName in PrinterSettings.InstalledPrinters) { if (IsVirtualPrinter(printerName)) { continue; } var snapshot = _statusReader.Read(printerName); printers.Add(new PrinterInfo { Id = index++, Name = printerName, DisplayName = printerName, IsActive = true, Status = snapshot.Status, QueueCount = snapshot.QueueCount, StatusMessage = snapshot.Message, LastHeartbeat = DateTime.Now }); } return printers; } public PrinterStatus GetPrinterStatus(string printerName) { if (string.IsNullOrWhiteSpace(printerName)) { return PrinterStatus.Unknown; } return _statusReader.Read(printerName).Status; } public async Task SubmitPrintJobAsync(PrintJob job, IProgress? progress = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(job); job.StartedAt = DateTime.Now; job.MarkPrinting(); progress?.Report(5); var executor = _executors.FirstOrDefault(item => item.CanHandle(job)) ?? throw new NotSupportedException($"没有可用的打印执行器处理文件:{job.FilePath}"); await executor.ExecuteAsync(job, progress, cancellationToken).ConfigureAwait(true); job.MarkCompleted(); return job.JobNo; } public bool CancelPrintJob(string jobId) { return !string.IsNullOrWhiteSpace(jobId); } public bool CancelPrintJob(PrintJob job) { ArgumentNullException.ThrowIfNull(job); return job.WindowsJobId is > 0 && _queueService.CancelJob(job.PrinterName, job.WindowsJobId.Value); } public PrintJobStatus GetJobStatus(string jobId) { return string.IsNullOrWhiteSpace(jobId) ? PrintJobStatus.Failed : PrintJobStatus.Pending; } private static bool IsVirtualPrinter(string printerName) { if (string.IsNullOrWhiteSpace(printerName)) { return true; } try { using var searcher = new ManagementObjectSearcher("SELECT Name, DriverName, PortName FROM Win32_Printer"); foreach (ManagementObject printer in searcher.Get()) { var name = Convert.ToString(printer["Name"]); if (!string.Equals(name, printerName, StringComparison.OrdinalIgnoreCase)) { continue; } var driverName = Convert.ToString(printer["DriverName"]) ?? string.Empty; var portName = Convert.ToString(printer["PortName"]) ?? string.Empty; return IsKnownVirtualPrinter(printerName, driverName, portName); } } catch { // If WMI is unavailable, fall back to conservative name-based filtering. } return IsKnownVirtualPrinter(printerName, string.Empty, string.Empty); } private static bool IsKnownVirtualPrinter(string printerName, string driverName, string portName) { var value = $"{printerName}|{driverName}|{portName}"; return ContainsAny(value, "Microsoft Print to PDF", "Microsoft XPS Document Writer", "OneNote", "Fax", "Foxit PDF", "PDF Printer", "Print to Evernote", "Evernote", "XPS", "PORTPROMPT:", "FILE:", "NUL:"); } private static bool ContainsAny(string value, params string[] candidates) { return candidates.Any(candidate => value.Contains(candidate, StringComparison.OrdinalIgnoreCase)); } }