Files
client/Services/Printing/WindowsPrintQueueService.cs

164 lines
5.4 KiB
C#
Raw Normal View History

2026-05-23 18:16:50 +08:00
using System.Management;
using System.Runtime.InteropServices;
using CloudPrint.Client.Helpers;
namespace CloudPrint.Client.Services.Printing;
public sealed class WindowsPrintQueueService : IWindowsPrintQueueService
{
public WindowsPrintQueueJob? FindLatestJob(string printerName, string documentName)
{
if (string.IsNullOrWhiteSpace(printerName) || string.IsNullOrWhiteSpace(documentName))
{
return null;
}
try
{
return EnumerateJobs(printerName)
.Where(job => job.DocumentName.Contains(documentName, StringComparison.OrdinalIgnoreCase)
|| documentName.Contains(job.DocumentName, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(job => job.JobId)
.FirstOrDefault();
}
catch
{
return null;
}
}
public bool CancelJob(string printerName, int jobId)
{
if (string.IsNullOrWhiteSpace(printerName) || jobId <= 0)
{
return false;
}
if (CancelJobWithWinSpool(printerName, jobId))
{
return true;
}
foreach (var job in QueryJobObjects(printerName))
{
var currentJobId = Convert.ToInt32(job["JobId"] ?? 0);
if (currentJobId != jobId)
{
continue;
}
job.InvokeMethod("Delete", null);
return true;
}
return false;
}
private static IEnumerable<WindowsPrintQueueJob> EnumerateJobs(string printerName)
{
var spoolerJobs = EnumerateJobsWithWinSpool(printerName);
if (spoolerJobs.Count > 0)
{
return spoolerJobs;
}
return EnumerateJobsWithWmi(printerName);
}
private static IReadOnlyList<WindowsPrintQueueJob> EnumerateJobsWithWinSpool(string printerName)
{
if (!WindowsPrintApi.OpenPrinter(printerName, out var printerHandle, IntPtr.Zero))
{
return Array.Empty<WindowsPrintQueueJob>();
}
try
{
const int maxJobs = 256;
_ = WindowsPrintApi.EnumJobs(printerHandle, 0, maxJobs, 1, IntPtr.Zero, 0, out var bytesNeeded, out _);
if (bytesNeeded <= 0)
{
return Array.Empty<WindowsPrintQueueJob>();
}
var buffer = Marshal.AllocHGlobal(bytesNeeded);
try
{
if (!WindowsPrintApi.EnumJobs(printerHandle, 0, maxJobs, 1, buffer, bytesNeeded, out _, out var jobsReturned))
{
return Array.Empty<WindowsPrintQueueJob>();
}
var jobs = new List<WindowsPrintQueueJob>(jobsReturned);
var itemSize = Marshal.SizeOf<WindowsPrintApi.JobInfo1>();
for (var index = 0; index < jobsReturned; index++)
{
var itemPointer = IntPtr.Add(buffer, index * itemSize);
var jobInfo = Marshal.PtrToStructure<WindowsPrintApi.JobInfo1>(itemPointer);
jobs.Add(new WindowsPrintQueueJob
{
JobId = jobInfo.JobId,
PrinterName = printerName,
DocumentName = jobInfo.DocumentName ?? string.Empty,
Status = string.IsNullOrWhiteSpace(jobInfo.StatusText) ? $"0x{jobInfo.Status:X}" : jobInfo.StatusText,
Position = jobInfo.Position,
TotalPages = jobInfo.TotalPages,
PagesPrinted = jobInfo.PagesPrinted
});
}
return jobs;
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
finally
{
_ = WindowsPrintApi.ClosePrinter(printerHandle);
}
}
private static bool CancelJobWithWinSpool(string printerName, int jobId)
{
if (!WindowsPrintApi.OpenPrinter(printerName, out var printerHandle, IntPtr.Zero))
{
return false;
}
try
{
return WindowsPrintApi.SetJob(printerHandle, jobId, 0, IntPtr.Zero, WindowsPrintApi.JobControlDelete)
|| WindowsPrintApi.SetJob(printerHandle, jobId, 0, IntPtr.Zero, WindowsPrintApi.JobControlCancel);
}
finally
{
_ = WindowsPrintApi.ClosePrinter(printerHandle);
}
}
private static IEnumerable<WindowsPrintQueueJob> EnumerateJobsWithWmi(string printerName)
{
foreach (var job in QueryJobObjects(printerName))
{
yield return new WindowsPrintQueueJob
{
JobId = Convert.ToInt32(job["JobId"] ?? 0),
PrinterName = printerName,
DocumentName = Convert.ToString(job["Document"]) ?? string.Empty,
Status = Convert.ToString(job["JobStatus"]) ?? string.Empty
};
}
}
private static IEnumerable<ManagementObject> QueryJobObjects(string printerName)
{
var escapedName = printerName.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("'", "''", StringComparison.Ordinal);
using var searcher = new ManagementObjectSearcher($"SELECT JobId, Document, JobStatus, Name FROM Win32_PrintJob WHERE Name LIKE '{escapedName},%'");
foreach (ManagementObject job in searcher.Get())
{
yield return job;
}
}
}