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));
}
}